Grafana es la herramienta de visualización más usada para series temporales: se conecta a una base de datos y convierte las lecturas en gráficas, indicadores y alertas. En este tutorial montarás en Ubuntu 24.04 un panel IoT completo: una base de datos PostgreSQL con lecturas de sensores, Grafana instalado desde su repositorio oficial y publicado con HTTPS, una fuente de datos aprovisionada por archivo, un dashboard con una variable para elegir sensores y una alerta cuando la temperatura supera un umbral.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Un dominio o subdominio (en esta guía your_domain) con un registro DNS A apuntando a la IP del servidor.
  • UFW activo con SSH permitido.
  • Opcional: una cuenta SMTP para enviar las alertas por correo.

En esta guía los datos de ejemplo se generan con SQL. En un proyecto real los insertaría tu pasarela IoT, por ejemplo Node-RED o Telegraf leyendo de un broker MQTT.

Paso 1: Preparar la base de datos de sensores

PostgreSQL es una buena base para datos de sensores: la consulta con SQL estándar y Grafana la soporta de forma nativa. Si más adelante necesitas compresión y particionado automático, la extensión TimescaleDB funciona sobre el mismo esquema.

Instala PostgreSQL 16 desde los repositorios de Ubuntu:

sudo apt update
sudo apt install postgresql

Abre una sesión de psql como el usuario administrador postgres:

sudo -u postgres psql

Crea la base de datos, la tabla de lecturas y un índice por sensor y fecha:

CREATE DATABASE iot;
\c iot

CREATE TABLE sensor_readings (
    time        timestamptz      NOT NULL,
    sensor_id   text             NOT NULL,
    temperature double precision,
    humidity    double precision
);

CREATE INDEX ON sensor_readings (sensor_id, time DESC);

Inserta dos días de lecturas simuladas, una por minuto para tres sensores:

INSERT INTO sensor_readings (time, sensor_id, temperature, humidity)
SELECT t,
       s,
       20 + 5 * sin(extract(epoch FROM t) / 3600) + random(),
       50 + 10 * random()
FROM generate_series(now() - interval '2 days', now(), interval '1 minute') AS t,
     unnest(ARRAY['salon', 'cocina', 'exterior']) AS s;
INSERT 0 8643

Crea un usuario de solo lectura para Grafana. Grafana no necesita escribir, y así un error en una consulta no puede modificar datos:

CREATE ROLE grafana_reader LOGIN PASSWORD 'your_strong_password';
GRANT CONNECT ON DATABASE iot TO grafana_reader;
GRANT SELECT ON sensor_readings TO grafana_reader;
\q

Comprueba el acceso con el nuevo usuario (te pedirá su contraseña):

psql "host=127.0.0.1 dbname=iot user=grafana_reader" -c "SELECT sensor_id, count(*) FROM sensor_readings GROUP BY 1;"
 sensor_id | count
-----------+-------
 cocina    |  2881
 exterior  |  2881
 salon     |  2881
(3 rows)

Paso 2: Instalar Grafana desde el repositorio oficial

Añade la clave y el repositorio de Grafana:

sudo apt install gnupg wget
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

Instala Grafana OSS:

sudo apt update
sudo apt install grafana

Paso 3: Configurar Grafana para funcionar detrás de un proxy

Grafana escuchará solo en localhost y Nginx servirá HTTPS. Edita el archivo principal:

sudo nano /etc/grafana/grafana.ini

En la sección [server], descomenta y ajusta estas líneas (en este archivo los comentarios empiezan por ;):

[server]
http_addr = 127.0.0.1
http_port = 3000
domain = your_domain
root_url = https://your_domain/

En la sección [users], confirma que el registro abierto está desactivado:

[users]
allow_sign_up = false

Arranca Grafana y habilítalo al inicio:

sudo systemctl daemon-reload
sudo systemctl enable --now grafana-server

Comprueba que responde en local:

curl -s http://127.0.0.1:3000/api/health
{
  "database": "ok",
  "version": "12.2.0",
  "commit": "..."
}

Paso 4: Publicar Grafana con Nginx y HTTPS

Instala Nginx y Certbot:

sudo apt install nginx certbot python3-certbot-nginx

Crea el sitio:

sudo nano /etc/nginx/sites-available/grafana
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }

    location /api/live/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

La ruta /api/live/ es el WebSocket que Grafana usa para actualizaciones en vivo. Activa el sitio, abre el firewall y solicita el certificado:

sudo ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'
sudo certbot --nginx -d your_domain

Abre https://your_domain, entra con el usuario admin y la contraseña admin, y define una contraseña nueva cuando Grafana la pida. Hazlo nada más publicar el servicio.

Paso 5: Aprovisionar la fuente de datos PostgreSQL

Puedes añadir la fuente de datos desde la interfaz, pero definirla en un archivo de aprovisionamiento la hace reproducible y la mantiene bajo control de versiones. Crea el archivo:

sudo nano /etc/grafana/provisioning/datasources/iot.yaml
apiVersion: 1

datasources:
  - name: IoT PostgreSQL
    type: grafana-postgresql-datasource
    uid: iot-postgres
    url: 127.0.0.1:5432
    user: grafana_reader
    jsonData:
      database: iot
      sslmode: disable
      postgresVersion: 1600
    secureJsonData:
      password: your_strong_password
    isDefault: true

sslmode: disable es aceptable porque la conexión no sale del servidor. Protege el archivo, que contiene la contraseña, y reinicia Grafana para que lo cargue:

sudo chown root:grafana /etc/grafana/provisioning/datasources/iot.yaml
sudo chmod 640 /etc/grafana/provisioning/datasources/iot.yaml
sudo systemctl restart grafana-server

En Grafana, ve a Connections > Data sources > IoT PostgreSQL y pulsa Save & test. Debe aparecer el mensaje Database Connection OK.

Paso 6: Crear el dashboard de sensores

Variable para elegir sensores

Crea un dashboard nuevo (Dashboards > New > New dashboard) y, antes de añadir paneles, abre Settings > Variables > Add variable:

  • Type: Query.
  • Name: sensor.
  • Data source: IoT PostgreSQL.
  • Query:
SELECT DISTINCT sensor_id FROM sensor_readings ORDER BY 1

Activa Multi-value e Include All option y guarda. Aparecerá un desplegable en la parte superior del dashboard con salon, cocina y exterior.

Panel de temperatura

Añade una visualización de tipo Time series. En el editor de consultas cambia a modo Code, elige el formato Time series y pega:

SELECT
  $__timeGroupAlias(time, $__interval),
  sensor_id AS metric,
  avg(temperature) AS value
FROM sensor_readings
WHERE $__timeFilter(time)
  AND sensor_id IN ($sensor)
GROUP BY 1, 2
ORDER BY 1

Las macros hacen el trabajo pesado:

MacroQué hace
$__timeFilter(time)Filtra por el rango de tiempo seleccionado en el dashboard.
$__timeGroupAlias(time, $__interval)Agrupa en intervalos que se ajustan al zoom, para no enviar miles de puntos al navegador.
$sensorSe sustituye por los sensores elegidos en el desplegable, ya entre comillas.

La columna metric genera una serie por sensor. En las opciones del panel, fija Unit en Celsius (°C).

Panel de última lectura

Añade un panel Stat para ver la temperatura actual de cada sensor, con formato Table:

SELECT DISTINCT ON (sensor_id)
  sensor_id,
  temperature
FROM sensor_readings
WHERE sensor_id IN ($sensor)
ORDER BY sensor_id, time DESC

DISTINCT ON devuelve la fila más reciente de cada sensor gracias al índice creado en el paso 1. En Value options, elige Show: All values para que se muestre un valor por fila (uno por sensor) y Fields: temperature. Añade umbrales (Thresholds) en 25 y 30 para que el color cambie de verde a naranja y rojo.

Guarda el dashboard. Cambia el rango de tiempo a Last 2 days y verás la oscilación de los datos simulados en cada sensor.

Paso 7: Configurar una alerta de temperatura

Para enviar alertas por correo, añade la configuración SMTP en /etc/grafana/grafana.ini:

sudo nano /etc/grafana/grafana.ini
[smtp]
enabled = true
host = smtp.example.com:587
user = [email protected]
password = your_smtp_password
from_address = [email protected]
from_name = Grafana IoT

Reinicia Grafana con sudo systemctl restart grafana-server. Después, en Alerting > Contact points, edita el punto de contacto por defecto, añade tu dirección de correo y pulsa Test para comprobar que llega el mensaje.

Crea la regla en Alerting > Alert rules > New alert rule. Como consulta, en modo Code y formato Table:

SELECT
  sensor_id,
  avg(temperature) AS value
FROM sensor_readings
WHERE time > now() - interval '5 minutes'
GROUP BY sensor_id

Cada fila genera una alerta independiente con la etiqueta sensor_id, así sabrás qué sensor ha superado el límite. Configura la condición como Threshold IS ABOVE 30, elige una carpeta y un grupo de evaluación cada 1 minuto con un periodo pendiente (Pending period) de 5 minutos para evitar avisos por picos puntuales.

Para probarla, inserta una lectura alta:

sudo -u postgres psql -d iot -c "INSERT INTO sensor_readings VALUES (now(), 'cocina', 45, 40);"

La regla pasa a estado Pending en el siguiente minuto y a Firing cuando se cumple el periodo pendiente, si la media de los últimos cinco minutos sigue por encima del umbral. Inserta varias lecturas si la media no llega a 30.

Solución de problemas

Database Connection OK no aparece: comprueba la contraseña del archivo de aprovisionamiento y que PostgreSQL escucha en 127.0.0.1:5432 con sudo ss -tlnp | grep 5432. El log de Grafana lo detalla con sudo journalctl -u grafana-server -n 50 --no-pager.

El panel muestra "No data": el rango de tiempo no cubre los datos o la variable sensor no tiene ningún valor seleccionado. Pulsa Query inspector en el panel para ver el SQL final con las macros sustituidas.

Redirecciones infinitas o recursos que no cargan: root_url no coincide con la URL pública. Debe ser exactamente https://your_domain/.

Gráficas lentas con muchos datos: no quites $__timeGroupAlias; sin agrupación Grafana descarga cada punto. Con millones de filas, valora TimescaleDB y sus agregados continuos.

Conclusión

Tienes Grafana funcionando en Ubuntu 24.04 detrás de Nginx con HTTPS, conectado a PostgreSQL con un usuario de solo lectura, un dashboard de sensores filtrable por variable y una alerta por correo cuando la temperatura supera el umbral. El esquema sirve tal cual para datos reales.

Como siguientes pasos puedes:

  • Enviar lecturas reales desde tus dispositivos a un broker MQTT y guardarlas en sensor_readings con Node-RED o Telegraf.
  • Exportar el dashboard como JSON y aprovisionarlo desde /etc/grafana/provisioning/dashboards junto a la fuente de datos.
  • Instalar la extensión TimescaleDB y convertir la tabla en hypertable cuando el volumen de datos crezca.