Skip to content
Learni
View all tutorials
Monitoring IoT

How to Implement Grafana for IoT in 2026

Lire en français

Introduction

Grafana is the go-to tool for visualizing real-time IoT data, especially with time-series sources like InfluxDB and protocols like MQTT. In 2026, with the explosion of edge sensors (over 75 billion projected), an expert Grafana implementation enables horizontal scalability, interactive dashboards, and proactive alerts on critical metrics like temperature, humidity, or energy consumption.

This expert tutorial guides you step-by-step to deploy a complete stack: Grafana + InfluxDB + Mosquitto MQTT + Telegraf. We use provisioning for declarative configuration, ideal for production Kubernetes or Docker Swarm. Each step includes working code, advanced Flux queries, and optimizations for 10k+ data points per second. By the end, you'll have a system monitoring simulated IoT data, ready to scale to real fleets. Why it matters: Poor IoT visualization costs 20-30% in downtime; Grafana cuts that with dynamic panels and ML-based anomaly detection.

Prerequisites

  • Docker and Docker Compose v2.20+
  • Advanced knowledge of InfluxDB 2.x (Flux/QL), MQTT v5, and YAML provisioning
  • Linux server with 4GB RAM minimum (16GB+ for production)
  • Tools: mosquitto_pub for MQTT testing, YAML/JSON editor
  • Grafana admin access (default: admin/admin)

Deploy the Docker Compose Stack

docker-compose.yml
version: '3.8'
services:
  influxdb:
    image: influxdb:2.7
    container_name: influxdb
    ports:
      - '8086:8086'
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=adminpass
      - DOCKER_INFLUXDB_INIT_ORG=iot
      - DOCKER_INFLUXDB_INIT_BUCKET=iot_bucket
      - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=token_influx
    volumes:
      - influxdb_data:/var/lib/influxdb2
    restart: unless-stopped

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    ports:
      - '3000:3000'
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    depends_on:
      - influxdb
    restart: unless-stopped

  mosquitto:
    image: eclipse-mosquitto:2.0
    container_name: mosquitto
    ports:
      - '1883:1883'
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf
    restart: unless-stopped

  telegraf:
    image: telegraf:1.28
    container_name: telegraf
    volumes:
      - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
    depends_on:
      - influxdb
      - mosquitto
    restart: unless-stopped

volumes:
  influxdb_data:
  grafana_data:

This docker-compose.yml deploys a complete stack: InfluxDB for storing IoT time-series, Grafana for visualization, Mosquitto as the MQTT broker, and Telegraf to ingest MQTT data into InfluxDB. Volumes persist data; tokens and org are ready for provisioning. Run with docker compose up -d. Pitfall: Check exposed ports to avoid firewall conflicts.

Verify Deployment and Configure MQTT

After docker compose up -d, access Grafana at http://localhost:3000 (admin/admin). InfluxDB UI: http://localhost:8086 (token: token_influx). Create a simple mosquitto.conf file:

plaintext
listener 1883
allow_anonymous true

Test MQTT: mosquitto_pub -h localhost -t iot/sensor/temp -m '25.3'. Telegraf will collect this into InfluxDB. This setup simulates an IoT sensor publishing every 10 seconds.

Provision InfluxDB Datasource

grafana/provisioning/datasources/influxdb.yml
apiVersion: 1

datasources:
  - name: InfluxDB-IoT
    type: influxdb
    access: proxy
    orgId: 1
    url: http://influxdb:8086
    version: Flux
    jsonData:
      defaultBucket: iot_bucket
      organization: iot
      tlsSkipVerify: true
    secureJsonData:
      token: token_influx
    isDefault: true
    editable: false

This YAML file automatically provisions the InfluxDB datasource in Grafana on startup. Use Flux for advanced IoT queries; proxy mode caches them. Place it in ./grafana/provisioning/datasources/. Expert advantage: Declarative and reproducible in CI/CD. Pitfall: Token is insecure in dev; use secrets in production via secureJsonData with Vault.

Telegraf Configuration for MQTT IoT

telegraf.conf
[[inputs.mqtt_consumer]]
  servers = ["tcp://mosquitto:1883"]
  topics = [
    "iot/sensor/#"
  ]
  data_format = "value"
  data_type = "float"

  [[inputs.mqtt_consumer.tags]]
    sensor_type = "${topic}"

[[outputs.influxdb_v2]]
  urls = ["http://influxdb:8086"]
  token = "token_influx"
  organization = "iot"
  bucket = "iot_bucket"

[[processors.enum]]
  [[processors.enum.mapping]]
    field = "value"
    destination = "temperature"
    [processors.enum.mapping.value_mappings]
      "25.3" = "25.3"

[[processors.starlark]]
  script = """
def apply(metric):
    metric.tags['location'] = 'factory'
    return metric
"""

Telegraf ingests MQTT topics iot/sensor/* into InfluxDB, parsing float values as the 'temperature' metric. Processors add tags (like location) and mappings for IoT context. Restart Telegraf after editing. Expert tip: Starlark for custom transformations (e.g., geolocation). Pitfall: Wildcard # topics can overload; filter with qos=1 in production.

Create and Test an IoT Dashboard

Publish MQTT data: while true; do mosquitto_pub -h localhost -t iot/sensor/temp -m $((25 + RANDOM % 5)); sleep 10; done. In Grafana > Dashboards > New > Import, use the JSON below. Panels include Graph for trends, Stat for thresholds, and Table for logs. Use Flux queries for windowed aggregations.

Complete IoT Dashboard in JSON

iot-dashboard.json
{
  "__inputs": [],
  "id": null,
  "title": "IoT Sensors Dashboard",
  "tags": [],
  "style": "dark",
  "timezone": "browser",
  "panels": [
    {
      "id": 1,
      "title": "Température Temps Réel",
      "type": "timeseries",
      "targets": [
        {
          "query": "from(bucket: "iot_bucket")
  |> range(start: v.timeRangeStart)
  |> filter(fn: (r) => r._measurement == "temperature")
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)",
          "refId": "A",
          "datasource": {
            "type": "influxdb",
            "uid": "InfluxDB-IoT"
          }
        }
      ],
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
      "fieldConfig": {
        "defaults": {
          "unit": "celsius",
          "color": { "mode": "palette-classic" }
        }
      }
    },
    {
      "id": 2,
      "title": "Stat Température Actuelle",
      "type": "stat",
      "targets": [
        {
          "query": "from(bucket: "iot_bucket")
  |> range(start: -1m)
  |> filter(fn: (r) => r._measurement == "temperature")
  |> last()",
          "refId": "A",
          "datasource": { "type": "influxdb", "uid": "InfluxDB-IoT" }
        }
      ],
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
      "fieldConfig": {
        "defaults": {
          "unit": "celsius",
          "thresholds": {
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 28 },
              { "color": "red", "value": 30 }
            ]
          }
        }
      }
    }
  ],
  "time": {
    "from": "now-1h",
    "to": "now"
  },
  "timepicker": {},
  "refresh": "10s",
  "schemaVersion": 39,
  "version": 1
}

This importable JSON dashboard shows a timeseries for temperature trends (mean/window aggregated) and a stat for the current value with color thresholds. 10s refresh for real-time IoT. aggregateWindow handles expert downsampling. Save and provision via /provisioning/dashboards/ for auto-loading. Pitfall: Queries without createEmpty: false clutter empty graphs.

Grafana Alert Rule for IoT

grafana/provisioning/alerting/iot-alerts.yml
apiVersion: 1
groups:
  - orgId: 1
    name: IoT Alerts
    folder: IoT
    interval: 60s
    rules:
      - uid: temp-high
        title: "Température IoT élevée"
        condition: B
        data:
          - refId: A
            queryType: ""
            relativeTimeRange:
              from: 600
              to: 0
            datasourceUid: "InfluxDB-IoT"
            model:
              expr: "from(bucket: \"iot_bucket\")
  |> range(start: -10m)
  |> filter(fn: (r) => r._measurement == \"temperature\")
  |> last()
  |> map(fn: (r) => ({ r with value: r._value }))
  |> filter(fn: (r) => r.value > 30.0)
              refId: B
              queryType: ""
              datasourceUid: "__expr__"
              model:
                conditions:
                  - evaluator:
                      params: [30]
                      type: gt
                    operator:
                      type: and
                    query:
                      params: ["A"]
                    reducer:
                      params: []
                      type: last
                type: query
        noDataState: NoData
        execErrState: Error
        for: 1m
        annotations:
          summary: "Temp > 30°C sur capteur IoT"
        labels:
          severity: critical
        record: temp_high
        notifications: []
        isPaused: false

This provisioned YAML rule alerts if the last temperature >30°C for 1m (debounce). Flux last() + filter for IoT precision; notify via email/Slack in production. Place in ./grafana/provisioning/alerting/. Expert tip: for: 1m avoids false positives from noisy data. Pitfall: Without range(-10m), you miss spikes; test in Grafana > Alerting.

Best Practices

  • Provisioning everywhere: Keep dashboards/datasources/alerts in Git as YAML/JSON for IaC and zero-downtime rollouts.
  • IoT Downsampling: Use InfluxDB Continuous Queries or aggregateWindow(every:1h) for long-term retention (1 year+).
  • MQTT Security: Enable TLS/ACLS on Mosquitto; rotate tokens via Vault.
  • Scalability: Grafana Enterprise cluster + InfluxDB IOx for 1M+ metrics/s; Loki for IoT logs.
  • ML Anomalies: Grafana ML plugin for outlier detection on sensors (e.g., abnormal temperature).

Common Errors to Avoid

  • No downsampling: InfluxDB databases explode (>1TB/month) without retention policies; limit infinite to 7d max.
  • Unoptimized queries: Flux without early filter scans everything; always start with |> filter(fn: (r) => r._measurement == "temp").
  • Overly aggressive refresh: 5s+ is fine for IoT, but <1s kills performance; use dashboard variables for dynamism.
  • Misconfigured CORS/Proxy: direct datasource exposes tokens; enforce proxy with NGINX reverse proxy.

Next Steps

Dive deeper with Grafana IoT Plugins (e.g., native MQTT datasource). Migrate to Grafana Cloud for managed IoT. Check the advanced InfluxDB Flux docs.

Explore our Learni DevOps & IoT training courses for Grafana Certified and edge computing stacks.