Come funziona lo streaming dalla webcam Reolink con Raspberry Pi e Nginx

Una guida per trasformare il flusso video RTSP di una webcam Reolink in uno streaming visibile su qualsiasi browser tramite un Raspberry Pi e il server web Nginx installato localmente.

1. Il problema da risolvere

Usiamo una webcam Reolink RLC-510A, un modello poco costoso ma che ha quanto ci serve per lo streaming live da esterno: POE (power over ethernet) e RTSP. Le webcam Reolink inviano video tramite RTSP, un protocollo usato per videosorveglianza e sistemi professionali.

Tuttavia:

RTSP NON funziona nei browser.
Chrome, Firefox, Safari non sanno aprirlo.

Il web funziona invece con:

HLS (HTTP Live Streaming)
✔ MPEG-DASH

Quindi serve un “traduttore”.

2. Struttura del sistema

Il sistema finale che abbiamo creato fa questo:

WEBCAMRTSPRaspberry PiFFmpegRTMPNginxHLSBrowser

Ogni elemento ha una funzione precisa.

3. I protocolli spiegati in modo semplice

RTSP – il linguaggio della webcam

È usato dalle webcam IP. Ottimo per sistemi di videosorveglianza, non compatibile con i browser. Sulla Reolink il flusso video RTSP è rtsp://admin:password@192.168.1.35:554//h264Preview_01_main

RTMP – il linguaggio per i server di streaming

RTMP è un protocollo per mandare video verso un server. I browser NON lo leggono. Lo usiamo come “ponte” tra FFmpeg e Nginx.

HLS – il linguaggio del browser

È lo standard attuale per lo streaming su web. Funziona così:

  • il video viene diviso in file .ts piccoli

  • un file .m3u8 li elenca

  • il browser li scarica in sequenza

È esattamente come funziona YouTube Live.

4. Ruolo del Raspberry Pi

Il Raspberry Pi svolge 3 compiti:

  1. Riceve il flusso RTSP dalla webcam
  2. Lo invia in RTMP al server interno Nginx
  3. Nginx lo converte in HLS

Diventa quindi un piccolo server streaming sempre acceso ed economico.

5. Lo script FFmpeg

FFmpeg è il software che prende il video RTSP e lo spedisce al server RTMP.

Script utilizzato reolink-rtsp-to-rtmp.sh da personalizzare il protocollo RTSP con il corretto ip locale della webcam e la password e la risorsa RTMP con un nome convenzionale del progetto live a cui si stalavorando:

#!/bin/bash

RTSP_URL="rtsp://admin:password@192.168.1.35:554//h264Preview_01_main"
RTMP_URL="rtmp://localhost/live/webcampergola"

exec ffmpeg -rtsp_transport tcp -i "$RTSP_URL" \
    -c:v copy -c:a aac -ar 44100 -b:a 128k \
    -f flv "$RTMP_URL"

Cosa fa?

  • prende il video dalla webcam

  • non lo ricodifica (risparmio CPU)

  • lo invia come RTMP a Nginx

6. Avvio automatico tramite systemd

Per avviare lo script alla partenza del Raspberry e in caso di crash occorre creare un servizio systemd in /etc/systemd/system/reolink-stream.service

[Unit]
Description=Reolink → RTSP → FFmpeg → RTMP bridge
After=network.target

[Service]
ExecStart=/usr/local/bin/reolink-rtsp-to-rtmp.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

7. Nginx con modulo RTMP

Nginx riceve RTMP e produce automaticamente HLS.

Configurazione del server web locale NGINX per gestire il protocollo RTMP:

rtmp {
    server {
        listen 1935;

        application live {
            live on;
            record off;

            hls on;
            hls_path /var/www/hls;
            hls_fragment 3s;
            hls_playlist_length 20s;
        }
    }
}

http {
    server {
        listen 8080;

        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            root /var/www;
            add_header Cache-Control no-cache;
            add_header 'Access-Control-Allow-Origin' '*' always;
        }
    }
}

Risultato generato:

webcampergola.m3u8 ← la playlist
webcampergola000.ts ← segmenti video
webcampergola001.ts

8. Inserimento nella pagina web (WordPress / Divi)

Con HLS.js si può vedere il flusso anche su Chrome.

Codice:

<video id="stream" controls autoplay muted playsinline width="100%"></video>

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
  var video = document.getElementById('stream');

  if (Hls.isSupported()) {
      var hls = new Hls();
      hls.loadSource('http://IP_RASPBERRY:8080/hls/webcampergola.m3u8');
      hls.attachMedia(video);
  } else {
      video.src = 'http://IP_RASPBERRY:8080/hls/webcampergola.m3u8';
  }
</script>

Se è tutto a posto aprendo la pagina si aprirà una finestra modale che richiederà il consenso per aprire il player, questo perchè il flusso ancora non è su https, per questo occorre un ulteriore passo che non affrontiamo qui.

9. Rendere lo streaming visibile dall’esterno

Servono 3 passaggi:

✔ 1. IP fisso del Raspberry

In DHCP reservation del router scegliere un ip fisso per la webcam.

✔ 2. Port forwarding

WAN 8080 → LAN 8080 → Raspberry.:Questo non serve più se andiamo a configurare https e quindi ad abilitare la porta 443 sulla Raspberry. Vedi in calce alla pagina l’Aggiornamento.

✔ 3. DDNS (DuckDNS, No-IP…)

Per ottenere un nome Internet stabile con cui configurare il Javascript del player HTML5 sul sito web. Installare il client DDNS sul Raspberry (consigliato) Invece di configurare il DDNS sul router (che a volte è limitato), il metodo più sicuro è: uno script sul Raspberry che aggiorna regolarmente l’IP pubblico sul DDNS.

C’è bisogno del token che viene rilasciato dal servizio DNS Lo script chiama un URL  periodicamente (es. ogni 5 minuti) per dire “il mio IP è questo”.

Ci vuole un piccolo script curl: /usr/local/bin/duckdns-update.sh

#!/bin/bash

DOMAIN="proloco-pergola"
TOKEN="IL_TUO_TOKEN"
URL="https://www.duckdns.org/update?domains=$DOMAIN&token=$TOKEN&ip="

curl -s "$URL" > /var/log/duckdns.log
date >> /var/log/duckdns.log
echo "DuckDNS aggiornato" >> /var/log/duckdns.log
echo "----------------" >> /var/log/duckdns.log

e una entry in crontab -e

*/5 * * * * /usr/local/bin/duckdns-update.sh >/dev/null 2>&1

Ora il Raspberry aggiornerà DuckDNS:

  • ad ogni reboot

  • ogni 5 minuti

  • ogni volta che l’IP pubblico cambia

11 Serve HTTPS

Tutto quanto sopra non funziona se non si abilita la gestione del protocollo https sul server web NGINX. Questo perchè non è possibile, oggi, avere senza conseguenze sul sito il “mixed content” ossia contenuti https (il sito fatto con Divi) e uno stream http.

WordPress è servito in HTTPS. Per evitare errore “Mixed Content”, anche il video deve essere caricato da una sorgente HTTPS. Ma il router TIM H388X spesso blocca o interferisce con le porte 80/443, impedendo a Let’s Encrypt di verificare i certificati tramite HTTP-01. Per questo abbiamo usato una soluzione alternativa e più potente: DNS-01 tramite DuckDNS.

1. Ottenere un certificato Let’s Encrypt con DNS-01 (DuckDNS)

Comando iniziale:

sudo certbot -d proloco-pergola.duckdns.org --manual --preferred-challenges dns certonly

Certbot chiede di aggiungere una entry TXT DNS. DuckDNS la accetta tramite richiesta HTTP:

https://www.duckdns.org/update?domains={YOURVALUE}&token={YOURVALUE}&txt={YOURVALUE}[&verbose=true][&clear=true]

Dove DOMAINS è il nostro dominio mappato siu duckdns.org, token è il nostro token sempre su duckdns.org e txt il valore restituito da certbot che attende fino a quando una volta fatta l’operazione e accertato che abbia avuto esito positivo non cliccate su enter e proseguite fino alla conclusione del processo di creazione dei certificati.

Una volta completato, i certificati vengono generati:

  • /etc/letsencrypt/live/proloco-pergola.duckdns.org/fullchain.pem

  • /etc/letsencrypt/live/proloco-pergola.duckdns.org/privkey.pem

Validità: 90 giorni.

2. Configurazione di Nginx per servire HTTPS + HLS

Ecco il blocco essenziale da inserire in nginx.conf:

server {
    listen 443 ssl;
    server_name proloco-pergola.duckdns.org;

    ssl_certificate /etc/letsencrypt/live/proloco-pergola.duckdns.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/proloco-pergola.duckdns.org/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location /hls {
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        alias /var/www/hls;
        add_header Cache-Control no-cache;
        add_header 'Access-Control-Allow-Origin' '*' always;
    }
}

server {
    listen 80;
    server_name proloco-pergola.duckdns.org;
    return 301 https://$host$request_uri;
}

Riavvio:

sudo nginx -t
sudo systemctl restart nginx

A questo punto la tua Raspberry serve lo stream in:

https://proloco-pergola.duckdns.org/hls/webcampergola.m3u8

Testabile da un cellulare in 4G.

3. Player HTML5 nel sito WordPress (con HLS.js)

Questo è il codice da inserire nella pagina Divi:

<video id="stream" controls autoplay muted playsinline width="100%"></video>

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
  var video = document.getElementById('stream');

  if (Hls.isSupported()) {
      var hls = new Hls();
      hls.loadSource('https://proloco-pergola.duckdns.org/hls/webcampergola.m3u8');
      hls.attachMedia(video);
  } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = 'https://proloco-pergola.duckdns.org/hls/webcampergola.m3u8';
  }
</script>

Funziona su:

  • iPhone
  • Android
  • Chrome / Firefox / Safari

4. Automazione completa del rinnovo SSL

Let’s Encrypt richiede il rinnovo ogni 90 giorni.
Abbiamo creato tre script:

4.1 Auth Hook  /usr/local/bin/duckdns-auth.sh

Crea TXT su DuchDNS

#!/bin/bash
# DuckDNS manual auth hook for certbot DNS-01 challenge

DOMAIN="proloco-pergola"
TOKEN="b8851967-de68-4aa9-85b9-180eba33565a"

echo "Creating TXT record on DuckDNS..."
curl -s "https://www.duckdns.org/update?domains=$DOMAIN&token=$TOKEN&txt=$CERTBOT_VALIDATION" > /dev/null

# Wait for DNS propagation
echo "Waiting 30 seconds for DNS propagation..."
sleep 30

Permessi: sudo chmod +x /usr/local/bin/duckdns-auth.sh

4.2 Cleanup Hook  /usr/local/bin/duckdns-clean.sh

Rimuove TXT

#!/bin/bash
# DuckDNS cleanup hook for certbot DNS-01

DOMAIN="proloco-pergola"
TOKEN="b8851967-de68-4aa9-85b9-180eba33565a"

echo "Removing TXT record from DuckDNS..."
curl -s "https://www.duckdns.org/update?domains=$DOMAIN&token=$TOKEN&txt=" > /dev/null

Permessi: sudo chmod +x /usr/local/bin/duckdns-clean.sh

4.3 Script rinnovo /usr/local/bin/duckdns-renew.sh

Renew completo (giornaliero)

#!/bin/bash

certbot renew \
  --manual \
  --preferred-challenges dns \
  --manual-auth-hook /usr/local/bin/duckdns-auth.sh \
  --manual-cleanup-hook /usr/local/bin/duckdns-clean.sh \
  --manual-public-ip-logging-ok \
  --deploy-hook "systemctl reload nginx" \
  -q

Permessi: sudo chmod +x /usr/local/bin/duckdns-*.sh

5. Cron job per rinnovo automatico

Apri cron:

sudo crontab -e

Aggiungi:
0 4 * * * /usr/local/bin/duckdns-renew.sh >> /var/log/duckdns-renew.log 2>&1

Il certificato si rinnoverà ogni notte alle 4:00, completamente senza intervento manuale.

RISULTATO FINALE

✔ La webcam Reolink trasmette via RTSP
✔ FFmpeg la converte in streaming HLS
✔ Nginx serve il video in modo stabile
✔ HTTPS garantito tramite Let’s Encrypt
✔ DuckDNS aggiorna automaticamente l’IP pubblico
✔ Il player HTML5 su WordPress funziona da ogni dispositivo
✔ Compatibilità totale con browser moderni

Il riisultato è questo:

(sembra niente!)

Streaming live da casa!

Il sistema è:

  • scalabile
  • sicuro
  • professionale
  • ideale per webcam pubbliche

E si manterrà aggiornato in automatico.

 

 

 

Documentazione completa – Streaming live Pro Loco Pergola (ultimo aggiornamento)

Questo documento descrive come reinstallare da zero il sistema di streaming
della webcam della Pro Loco Pergola, includendo errori commessi, soluzioni
corrette e servizi utilizzati.

1. Obiettivo e panoramica

L’obiettivo è pubblicare su Internet il flusso video della telecamera
Reolink in modalità HLS (HTTP Live Streaming), in modo
sicuro (HTTPS) e senza dover aprire porte sul router 4G TIM.

Architettura finale:

  • Telecamera Reolink (flusso RTSP)
  • FFmpeg che converte RTSP → RTMP
  • Nginx con modulo RTMP che genera HLS:
  • File .ts e index .m3u8 in /var/www/hls
  • Nginx espone /hls in HTTPS con certificato Let’s Encrypt
  • Cloudflare Tunnel pubblica il sito
    https://prolocopergola.stream senza port forwarding

URL finale dello streaming:

https://prolocopergola.stream/hls/webcampergola.m3u8

2. Servizi utilizzati (pagamento e gratuiti)

2.1 Servizi a pagamento utilizzati

  • NordVPN – IP dedicato
    Fornisce un IP pubblico statico (es. 89.18.x.x), necessario perché:

    • con SIM 4G TIM si è dietro CGNAT, quindi nessuna porta è effettivamente raggiungibile dall’esterno;
    • Let’s Encrypt e i servizi esterni devono raggiungere il server tramite un IP “vero”.
  • Cloudflare – dominio prolocopergola.stream
    Il dominio è registrato e gestito da Cloudflare, che fornisce:

    • DNS affidabile;
    • integrazione con Cloudflare Tunnel;
    • certificati HTTPS lato Cloudflare quando serve.

2.2 Servizi NON più usati (e perché)

  • DuckDNS.org
    Usato all’inizio per DNS dinamico, ma:

    • non è gestibile da Cloudflare DNS;
    • non è ideale per Let’s Encrypt quando si passa attraverso VPN/tunnel;
    • non si integra con i Public Hostname di Cloudflare Tunnel.

    Conclusione: sostituito da un dominio nativo Cloudflare.

  • OpenVPN + DuckDNS
    Tentativo iniziale: creare una VPN e usare DuckDNS per raggiungere il Raspberry.

    Problemi:

    • Routing complesso e poco stabile;
    • difficoltà nel gestire HTTPS “pulito”;
    • non necessario una volta adottato Cloudflare Tunnel.

    Conclusione: architettura abbandonata. Ora si usa solo NordVPN (IP dedicato) + Cloudflare Tunnel.

3. Schema architetturale finale

Reolink RTSP
   ↓
FFmpeg (RTSP → RTMP)
   ↓
Nginx + RTMP Module (app "live")
   ↓
HLS (segmenti .ts + playlist .m3u8 in /var/www/hls)
   ↓
Nginx HTTPS (server_name prolocopergola.stream, location /hls)
   ↓
Cloudflare Tunnel (tunnel <→> prolocopergola.stream)
   ↓
Internet (browser, VLC, player web)

4. Errori principali commessi e cosa NON rifare

4.1 Cercare di aprire porte sul router 4G (port forwarding)

Con una SIM 4G TIM sei dietro CGNAT. Il port forwarding sul router
non funziona perché:

  • l’IP pubblico è condiviso tra molti utenti;
  • non hai il controllo del NAT del provider.

Lezione: scordarsi il port forwarding con SIM 4G, usare tunnel (Cloudflare) o IP dedicato VPN.

4.2 Usare DuckDNS con Cloudflare Tunnel

Cloudflare Tunnel si integra al meglio con domini gestiti nei DNS Cloudflare.
DuckDNS non lo è, quindi:

  • non si possono creare hostname supportati nativamente dal tunnel;
  • certificati e routing diventano complicati.

4.3 Configurazioni miste/rotte di nginx

Alcuni problemi trovati:

  • nginx.conf con caratteri non UTF-8 → Certbot falliva;
  • blocchi server duplicati o fuori dal blocco http { ... };
  • alias senza slash finale (es. alias /var/www/hls invece di /var/www/hls/).

4.4 Tunnel Cloudflare configurato con dominio non Cloudflare

Tentativi con *.duckdns.org e altri hostname non gestiti da Cloudflare
portavano a errori tipo:

  • SSL_ERROR_SYSCALL
  • 502 Bad Gateway

Soluzione corretta: usare un dominio risultante da registrazione Cloudflare
(es. prolocopergola.stream).


5. Reinstallazione da zero – Passi tecnici

5.1 Prerequisiti sul Raspberry Pi

Eseguire aggiornamenti e installare i pacchetti base:

sudo apt update
sudo apt upgrade -y

sudo apt install -y \
  ffmpeg \
  nginx \
  libnginx-mod-rtmp \
  curl

5.2 Creazione cartella HLS

sudo mkdir -p /var/www/hls
sudo chown www-data:www-data /var/www/hls
sudo chmod 755 /var/www/hls

5.3 Configurazione Nginx con modulo RTMP + HTTPS

File principale: /etc/nginx/nginx.conf
Una configurazione funzionale (da adattare se necessario) potrebbe essere:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

# RTMP block
rtmp {
    server {
        listen 1935;
        chunk_size 4096;

        application live {
            live on;
            record off;

            # Permette al Raspberry nella LAN di pubblicare lo stream
            allow publish 127.0.0.1;
            allow publish 192.168.0.0/16;
            deny publish all;

            hls on;
            hls_path /var/www/hls;
            hls_fragment 3;
            hls_playlist_length 20;

            allow play all;
        }
    }
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    sendfile on;

    # Server HTTPS locale
    server {
        listen 443 ssl;
        server_name prolocopergola.stream;

        ssl_certificate /etc/letsencrypt/live/prolocopergola.stream/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/prolocopergola.stream/privkey.pem;

        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            alias /var/www/hls/;
            add_header Cache-Control no-cache;
            add_header Access-Control-Allow-Origin "*" always;
        }
    }
}

Verifica della configurazione:

sudo nginx -t
sudo systemctl restart nginx

6. FFmpeg – Script per RTSP → RTMP

6.1 Script shell

Posizione suggerita: /usr/local/bin/reolink-rtsp-to-rtmp.sh

#!/bin/bash

# URL RTSP della Reolink
RTSP_URL="rtsp://admin:LA_TUA_PASSWORD@192.168.1.55:554/h264Preview_01_main"

# RTMP verso Nginx RTMP locale
RTMP_URL="rtmp://localhost/live/webcampergola"

exec ffmpeg -rtsp_transport tcp -i "$RTSP_URL" \
  -c:v copy -c:a aac -ar 44100 -b:a 128k \
  -f flv "$RTMP_URL"

Rendere eseguibile:

sudo chmod +x /usr/local/bin/reolink-rtsp-to-rtmp.sh

7. Servizio systemd per FFmpeg

7.1 File di servizio

Posizione: /etc/systemd/system/reolink-stream.service

[Unit]
Description=Reolink - RTSP - FFmpeg - RTMP bridge
After=network-online.target nginx.service
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/reolink-rtsp-to-rtmp.sh
Restart=always
RestartSec=5
StartLimitIntervalSec=0

[Install]
WantedBy=multi-user.target

Abilitazione e avvio:

sudo systemctl daemon-reload
sudo systemctl enable --now reolink-stream.service

# Verifica
sudo systemctl status reolink-stream.service

8. Cloudflare Tunnel – Configurazione

8.1 Installazione cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb

8.2 Login e creazione tunnel

cloudflared login
# Seguire il link nel browser e associare l'account Cloudflare

# Creazione tunnel
cloudflared tunnel create webcampergola

Annotare l’UUID del tunnel (es.
5934ef68-010c-4e61-89cf-f5e0e43c8448).

8.3 File di configurazione /etc/cloudflared/config.yml

tunnel: 5934ef68-010c-4e61-89cf-f5e0e43c8448
credentials-file: /home/mauro/.cloudflared/5934ef68-010c-4e61-89cf-f5e0e43c8448.json
origincert: /home/mauro/.cloudflared/cert.pem

ingress:
  - hostname: prolocopergola.stream
    service: https://localhost:443
  - service: http_status:404

8.4 Servizio systemd per cloudflared

sudo cloudflared service install
sudo systemctl enable --now cloudflared

# Verifica
sudo systemctl status cloudflared

9. Test finale e comandi di controllo

9.1 Controllo generazione HLS

Verificare che i file .ts e .m3u8 siano in /var/www/hls:

ls -l /var/www/hls

9.2 Test playlist locale (sul Raspberry)

curl -k -I https://localhost/hls/webcampergola.m3u8

9.3 Test playlist pubblica via Cloudflare

curl -I https://prolocopergola.stream/hls/webcampergola.m3u8

9.4 Comandi rapidi di manutenzione

  • Riavvia solo streaming FFmpeg:
    sudo systemctl restart reolink-stream.service
  • Riavvia Nginx:
    sudo systemctl restart nginx
  • Riavvia Cloudflare Tunnel:
    sudo systemctl restart cloudflared
  • Riavvio totale Raspberry:
    sudo reboot

10. URL finale e uso con VLC / player web

URL HLS pubblico definitivo:

https://prolocopergola.stream/hls/webcampergola.m3u8

Può essere usato:

  • in VLC: Media → Apri flusso di rete → incollare l’URL;
  • in un player HTML5 con hls.js;
  • integrato nel sito ufficiale della Pro Loco.

Se tutti i test restituiscono HTTP 200 e la cartella HLS è popolata di
file .ts aggiornati ogni pochi secondi, il sistema è correttamente
installato e pronto per l’uso continuativo.