Grafana is an open-source visualization platform that turns time-series data into dashboards and alerts. For IoT projects it sits on top of a database such as InfluxDB and shows sensor readings as graphs, gauges and tables, and it can notify you when a value crosses a threshold. In this tutorial you will install Grafana on Ubuntu 24.04, connect it to an InfluxDB 2 bucket that already receives sensor data, build a dashboard with a per-device selector, and create an alert that emails you when a room gets too hot.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges and UFW enabled. - InfluxDB 2 on the same server with an organization called
iotand a bucket callediotthat receives sensor data. This guide uses the schema produced by a Telegraf MQTT pipeline: measurementenvironment, tagdevice, and the fieldstemperature,humidityandbattery. Adjust the names in the queries if your data differs. - The
influxCLI configured with an operator token (it is after runninginflux setup). - For email alerts: an SMTP account (host, port, username and password).
Step 1 - Installing Grafana
Install Grafana from Grafana Labs' official APT repository so you receive updates with apt. Add the signing key and the repository:
sudo apt update
sudo apt install apt-transport-https wget gnupg
sudo install -m 0755 -d /etc/apt/keyrings
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" \
| sudo tee /etc/apt/sources.list.d/grafana.list
Install the open-source edition and start it:
sudo apt update
sudo apt install grafana
sudo systemctl enable --now grafana-server
Check that the service is running and listening on port 3000:
systemctl is-active grafana-server
sudo ss -tlnp | grep 3000
active
LISTEN 0 4096 *:3000 *:* users:(("grafana",...))
Allow the port in the firewall:
sudo ufw allow 3000/tcp
Open http://your_server_ip:3000 in a browser and log in with admin / admin. Grafana asks you to set a new password immediately; choose a strong one.
NotePlain HTTP sends your password unencrypted. For anything beyond a first test, put Grafana behind a reverse proxy such as Nginx or Caddy with a TLS certificate, and close port 3000 again.
Step 2 - Creating a read-only InfluxDB token
Grafana only needs to read data, so give it a token limited to reading the iot bucket. Look up the bucket ID:
influx bucket list --org iot --name iot
Create the token, replacing your_bucket_id with the ID from the output:
influx auth create --org iot --read-bucket your_bucket_id --description "grafana read"
Copy the value in the Token column. If you also downsample into a second bucket such as iot_hourly, add another --read-bucket flag with its ID.
Step 3 - Provisioning the InfluxDB data source
You can add data sources in the web interface, but a provisioning file keeps the configuration in version-controllable text and survives reinstalls. Create it:
sudo nano /etc/grafana/provisioning/datasources/influxdb-iot.yaml
apiVersion: 1
datasources:
- name: InfluxDB-IoT
type: influxdb
access: proxy
url: http://127.0.0.1:8086
isDefault: true
jsonData:
version: Flux
organization: iot
defaultBucket: iot
secureJsonData:
token: your_grafana_read_token
The file contains a secret, so only root and the grafana group may read it:
sudo chown root:grafana /etc/grafana/provisioning/datasources/influxdb-iot.yaml
sudo chmod 0640 /etc/grafana/provisioning/datasources/influxdb-iot.yaml
sudo systemctl restart grafana-server
In Grafana, go to Connections > Data sources, open InfluxDB-IoT and click Test at the bottom of the page. A green message saying the data source is working, together with the number of buckets found, confirms that the URL and token are correct.
Step 4 - Adding a device variable
A dashboard variable lets one dashboard show any device from a drop-down, instead of building a copy per sensor. Create a new dashboard with Dashboards > New > New dashboard, open Settings > Variables and click Add variable:
- Variable type: Query
- Name:
device - Data source: InfluxDB-IoT
- Query:
import "influxdata/influxdb/schema"
schema.tagValues(bucket: "iot", tag: "device")
- Multi-value: enabled
- Include All option: enabled
The preview at the bottom lists the devices found in the bucket, for example bedroom, garage and livingroom. Save the dashboard. In the queries below, ${device:json} expands the selected values into a JSON array that Flux's contains() function can test against.
Step 5 - Building the sensor panels
Add each panel with Add > Visualization, paste the query into the query editor, pick the visualization type on the right and set the options listed. v.timeRangeStart, v.timeRangeStop and v.windowPeriod are filled in by Grafana from the dashboard's time picker and panel width, so the same query works for the last hour or the last month.
Temperature over time
Visualization: Time series. Unit: Temperature > Celsius (°C).
from(bucket: "iot")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "environment" and r._field == "temperature")
|> filter(fn: (r) => contains(value: r.device, set: ${device:json}))
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|> keep(columns: ["_time", "_value", "device"])
aggregateWindow averages the raw points into one per pixel column, which keeps the panel fast over long ranges. You get one line per selected device.
Current humidity
Visualization: Gauge. Unit: Misc > Humidity (%H), Min 0, Max 100. Add thresholds, for example green from base, orange at 60 and red at 70.
from(bucket: "iot")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "environment" and r._field == "humidity")
|> filter(fn: (r) => contains(value: r.device, set: ${device:json}))
|> last()
|> keep(columns: ["_value", "device"])
The fixed -1h range shows the last value reported in the past hour regardless of the time picker; a gauge that goes empty means the device stopped reporting.
Battery levels
Visualization: Table. Under Overrides, set the _value field's unit to Percent (0-100) and its cell type to a colored background with thresholds red at base, orange at 20 and green at 50.
from(bucket: "iot")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "environment" and r._field == "battery")
|> last()
|> group()
|> keep(columns: ["device", "_value", "_time"])
|> sort(columns: ["_value"])
group() merges the per-device tables into one, so the panel shows a single table sorted from the emptiest battery upward. This panel ignores the device variable on purpose: battery status is most useful as a list of every device.
Save the dashboard and switch the device drop-down between values to check that the time series and gauge panels follow it.
Step 6 - Configuring SMTP for alert emails
Grafana needs an SMTP server to send email. Edit the configuration file:
sudo nano /etc/grafana/grafana.ini
Find the [smtp] section, which is commented out by default, and set these keys (remove the leading ; on each line you change):
[smtp]
enabled = true
host = smtp.your_provider.com:587
user = your_smtp_user
password = """your_smtp_password"""
from_address = grafana@your_domain
from_name = Grafana IoT
The triple quotes allow characters such as # and ; in the password, which Grafana would otherwise treat as comments. Restart Grafana:
sudo systemctl restart grafana-server
In the web interface, go to Alerting > Contact points, click Add contact point, choose Email, enter your address and click Test. A test email should arrive within a minute; if it does not, check sudo journalctl -u grafana-server -n 50 --no-pager for SMTP errors. Save the contact point.
Step 7 - Creating a temperature alert
Alert rules run on the Grafana server on a schedule, independently of whether anyone has the dashboard open. Go to Alerting > Alert rules > New alert rule and fill in:
- Name:
High room temperature. - Query A, data source InfluxDB-IoT:
from(bucket: "iot")
|> range(start: -10m)
|> filter(fn: (r) => r._measurement == "environment" and r._field == "temperature")
|> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
- Expressions: keep the default Reduce expression set to Last of
A, and set the Threshold expression to fire when the reduced value is above30. Because the query returns one series per device, Grafana creates one alert instance per device and includes thedevicelabel in notifications. - Folder and Evaluation group: create a group such as
iotwith an evaluation interval of1m. - Pending period:
5m, so a single spike does not trigger an email. - Contact point: the email contact point you created.
Save the rule. On the Alert rules page its state should be Normal. To test the whole chain, temporarily change the threshold to a value below the current temperature: after the pending period the state becomes Firing and the email arrives. Set the threshold back afterwards.
TipIn the rule's configure no data and error handling options, set the state for No data to Alerting. The rule then also warns you when sensors stop reporting, which with battery-powered devices happens more often than overheating.
Step 8 - Sharing a read-only dashboard
To show the dashboard to someone without a Grafana account, open it, click Share and choose Share externally (called Public dashboard in older Grafana versions). Grafana generates a URL that renders the dashboard read-only without logging in.
Before enabling it, keep in mind that anyone with the link can see the data and that dashboards using template variables are shared with their default values only. For people who need to change filters, create Grafana users with the Viewer role instead.
Troubleshooting
The data source test fails with 401 Unauthorized. The token is wrong or cannot read the bucket. List tokens with influx auth list and check that the Grafana token has read permission on iot.
Panels say "No data". Widen the time range to confirm data exists, then run the same Flux query with influx query on the server. Watch for typos in measurement, field and tag names, which are case sensitive.
The device drop-down is empty. Run the variable query with influx query. If it returns nothing, the data has no device tag; check how Telegraf builds the tag.
Provisioned data source changes are not applied. Grafana reads provisioning files at startup. Restart grafana-server after editing them and check the journal for YAML errors.
Conclusion
Grafana now reads your sensor data from InfluxDB with a least-privilege token, shows it on a reusable dashboard with a device selector, and emails you when a room overheats. Next, put Grafana behind a TLS reverse proxy, add a dashboard that reads a downsampled bucket for long-term trends, and add Slack or Telegram contact points for faster notifications.
