---
title: "Raspberry Pi e Cloudflare Tunnel: guida pratica con Access e avvio automatico"
source: "https://www.matteovincenti.com/blog/raspberry-pi-cloudflare-tunnel-access"
language: it
author: "Matteo Vincenti"
published: 2026-10-28
category: "Codice"
tags: ["Raspberry Pi", "Cloudflare", "Sicurezza"]
---

# Raspberry Pi e Cloudflare Tunnel: guida pratica con Access e avvio automatico

Collega un Raspberry Pi a Cloudflare Tunnel: dominio, DNS, accesso con Cloudflare Access, prove di sicurezza e avvio automatico con systemd.

![Una scheda Raspberry Pi stilizzata è collegata a una nuvola attraverso un tunnel verde con lucchetto.](https://cdn.sanity.io/images/xrqkwry7/production/ab67d193fa6fb73d3bff8d61a0d7d9e450db6568-1731x909.png?w=1400&fit=max&auto=format)

Categoria: Codice · Tag: Raspberry Pi, Cloudflare, Sicurezza

Un Raspberry Pi può ospitare una piccola applicazione web e renderla raggiungibile attraverso un indirizzo HTTPS, senza aprire una porta in ingresso sul router. Prima di configurare il collegamento serve però una decisione: la pagina deve essere pubblica, oppure deve essere accessibile soltanto a persone autorizzate?

In questa guida realizziamo il secondo caso. Partiamo da una pagina HTML innocua, colleghiamo il Raspberry a Cloudflare Tunnel e usiamo Cloudflare Access per richiedere il login. Poi aggiungiamo un client macchina e l’avvio automatico, come estensioni separate. Ogni passaggio ha un controllo: l’obiettivo è capire dove passa una richiesta e riconoscere quale componente non funziona.

L’esempio può diventare il punto di partenza per un piccolo portale interno, un’anteprima di progetto o un servizio di laboratorio. La pagina dimostrativa serve a verificare il percorso prima di collegare un’applicazione con dati reali.

## Che cosa collegano DNS, Tunnel e Access

Il dominio è il nome registrato, per esempio `example.com`. Il registrar gestisce la registrazione e la delega ai nameserver; il provider DNS autorevole pubblica i record. Possono essere aziende diverse. Usare Cloudflare per il DNS non obbliga a trasferirgli la registrazione del dominio.

Il sottodominio `lab.example.com` identifica il servizio. Il suo record DNS porta il visitatore a Cloudflare; la route del tunnel associa quel nome all’origine locale, cioè al server HTTP sul Raspberry.

`cloudflared` è il programma che apre e mantiene le connessioni in uscita dal Raspberry verso Cloudflare. Le richieste arrivano attraverso quel collegamento. Non occorrono un IP pubblico fisso o un servizio di DNS dinamico per questo percorso.

Access decide invece chi può raggiungere l’applicazione. Il browser incontra il controllo di identità prima che la richiesta venga inoltrata all’origine. Nel laboratorio abilitiamo anche la verifica del token Access nel connettore: Cloudflare firma un’attestazione dell’accesso, chiamata JWT, e `cloudflared` la controlla prima di contattare il server locale.

Il browser usa HTTPS verso Cloudflare; anche il collegamento del tunnel è cifrato. L’ultimo tratto sarà HTTP su `127.0.0.1`, all’interno dello stesso Raspberry. Se sposti l’origine su un’altra macchina, devi rivalutare quel tratto e la sua protezione.

## Prerequisiti e nomi del laboratorio

Il punto di partenza è un Raspberry già avviato con Raspberry Pi OS o Debian a 64 bit, Bash, `systemd`, accesso `sudo` e connessione Internet. L’installazione del sistema operativo e l’accesso SSH iniziale precedono questa guida.

Servono inoltre un account Cloudflare, un dominio che puoi amministrare, accesso al registrar se devi cambiare nameserver e una casella email da autorizzare in Access. La procedura usa una zona DNS in modalità completa, gestita da Cloudflare.

Il laboratorio rientra nel piano DNS Free e in Zero Trust Free, che al momento prevede un limite di 50 utenti. Dominio, hardware e connessione restano costi separati. L’attivazione di Zero Trust richiede di scegliere un piano e inserire i dati di pagamento anche scegliendo Free; Cloudflare specifica che il piano gratuito non comporta un addebito. Controlla le [condizioni del piano](https://www.cloudflare.com/plans/) e i [requisiti di attivazione](https://developers.cloudflare.com/cloudflare-one/setup/) prima di procedere.

Tutti i comandi Linux, compresi i controlli con `dig` e `curl`, vanno eseguiti sul Raspberry di laboratorio. Puoi usare tre terminali o sessioni SSH: uno per il server web, uno per il connettore e uno per i controlli. Per le prove da una connessione esterna basta il browser di un altro dispositivo.

`example.com` è un dominio riservato alla documentazione. Sostituisci `lab.example.com` con un sottodominio libero del tuo dominio, mantenendo lo stesso nome in DNS, Access, route e comandi.

Controlla l’ambiente:

```bash
cat /etc/os-release
dpkg --print-architecture
getconf LONG_BIT
systemctl --version
```

Sul Raspberry previsto qui, l’architettura dei pacchetti è `arm64` e la dimensione è `64`. Non installare un pacchetto `amd64` seguendo istruzioni per un normale PC.

Installa sul laboratorio gli strumenti usati nei passaggi successivi, se mancanti:

```bash
sudo apt-get update
sudo apt-get install python3 curl ca-certificates dnsutils iproute2
```

La rete deve consentire DNS e le connessioni in uscita del tunnel sulla porta `7844`: UDP per QUIC e TCP per HTTP/2. Il protocollo automatico può ripiegare su HTTP/2 se UDP non è disponibile. Per la verifica Access serve anche HTTPS in uscita verso il dominio della tua organizzazione `NOME_TEAM.cloudflareaccess.com`. Un firewall restrittivo deve seguire la [lista ufficiale delle destinazioni Cloudflare](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/tunnel-with-firewall/). Non aprire `18080` sul router: è la porta locale dell’esempio.

## Scegliere il dominio e decidere dove registrarlo

Per il laboratorio useremo un indirizzo come `lab.example.com`. Registri il dominio `example.com`; `lab` è un sottodominio che crei nella sua zona DNS. Non serve acquistare un dominio per ciascuna applicazione.

Cloudflare comprende servizi diversi: può registrare il dominio, gestirne il DNS, ricevere le richieste web e controllare chi accede all’applicazione. In questa guida useremo DNS, Tunnel e Access. La registrazione presso Cloudflare è facoltativa.

Il percorso dipende da dove parti:

_Scegliere il dominio e decidere dove registrarlo_

| Situazione | Operazione necessaria |
| --- | --- |
| Hai già un dominio attivo su Cloudflare | Controlla i record esistenti e passa alla configurazione del laboratorio. |
| Hai un dominio registrato altrove | Mantieni la registrazione dove si trova, aggiungi la zona a Cloudflare e cambia i nameserver presso il registrar. |
| Non hai un dominio | Registrane uno con Cloudflare Registrar oppure con un registrar che consenta di usare nameserver esterni. |

Il registrar continua a occuparsi del rinnovo del nome. Spostare il DNS a Cloudflare non sposta il sito su un altro hosting e non trasferisce le caselle email: i record devono continuare a indicare i rispettivi servizi.

### Registrare un dominio nuovo

Puoi acquistarlo direttamente dalla dashboard Cloudflare, in **Domains → Registrations → Buy domain**. Si apre **Register domain**: cerca il nome, confronta le disponibilità e seleziona quello che vuoi acquistare. Prima di confermare controlla prezzo iniziale, rinnovo, durata e contatti del registrante. Il dominio è a pagamento anche se userai il piano DNS Free.

Cloudflare non registra tutte le estensioni: consulta l’[elenco dei TLD supportati](https://developers.cloudflare.com/registrar/top-level-domains/). Il prezzo dipende dal nome e dall’estensione; non va dedotto dal costo di un altro dominio.

Completa le verifiche email richieste e controlla il rinnovo automatico, attivo per impostazione predefinita. I domini acquistati tramite Cloudflare Registrar usano già i nameserver Cloudflare. Finché la registrazione rimane lì, non puoi sostituirli con quelli di un altro provider DNS. La [procedura ufficiale di registrazione](https://developers.cloudflare.com/registrar/get-started/register-domain/) descrive requisiti e conferma dell’acquisto.

Puoi anche registrare il dominio presso un altro fornitore. Verifica prima che permetta di modificare i nameserver: sarà quel pannello, non quello dell’hosting o del Raspberry, a servirti per la delega del passaggio seguente.

### Collegare a Cloudflare un dominio esistente

Nel percorso scelto qui, chiamato **full setup**, **i nameserver autorevoli del dominio devono diventare quelli assegnati da Cloudflare**. La registrazione può rimanere presso il fornitore attuale. È il setup ordinario disponibile con il piano DNS Free; la documentazione distingue anche altri tipi di configurazione, che non seguiremo in questo laboratorio. [Full setup](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/).

Nella dashboard osservata per questa guida il percorso è **Domains → Overview → Add domain → Connect a domain**. Alcune versioni della documentazione lo chiamano **Onboard a domain**. Scegli il collegamento del dominio: **Transfer a domain** trasferirebbe invece la registrazione.

Inserisci il dominio principale, per esempio `example.com`, senza `https://`, percorsi o `lab`. Nel riquadro **Import DNS records** puoi scegliere la scansione automatica, l’inserimento manuale oppure un file di zona. Per partire da un dominio già utilizzato, la scansione è un aiuto, ma va confrontata con i record del provider attuale.

![Opzioni Cloudflare per importare i record DNS.](https://cdn.sanity.io/images/xrqkwry7/production/644a09192a5c7da38a5b94dd77a5de25c57b330a-706x308.jpg?w=1400&fit=max&auto=format)

Importazione DNS: la scansione automatica trova i record comuni. Le altre opzioni sono inserimento manuale e importazione di un file di zona.

Prosegui con **Continue** e scegli il piano Free quando richiesto. L’onboarding può mostrare anche opzioni sui crawler e sul file `robots.txt`: riguardano il comportamento del sito e non realizzano la delega DNS. Valutale per il tuo sito senza confonderle con l’autorizzazione degli utenti in Access.

Prima di cambiare nameserver, salva l’inventario DNS e verifica in Cloudflare:

- i record del sito principale, di `www` e degli altri sottodomini utilizzati;
- i record di posta MX, SPF, DKIM e DMARC;
- le verifiche TXT e gli eventuali record CAA richiesti dai servizi esistenti.

Mantieni i valori indicati dai rispettivi provider. La scansione può omettere nomi personalizzati, compresi alcuni record DKIM: un’importazione incompleta può interrompere sito o posta quando cambia il DNS autorevole. [Limiti della scansione automatica](https://developers.cloudflare.com/dns/zone-setups/reference/dns-quick-scan/).

Controlla anche **Proxy status** dei record A, AAAA e CNAME importati. **Proxied**, con la nuvola arancione, fa passare il traffico web attraverso Cloudflare; **DNS only**, con la nuvola grigia, restituisce il record senza usare il proxy. Gli indirizzi dei server di posta e i CNAME usati per verifiche o DKIM devono restare **DNS only** secondo le istruzioni del provider. MX e TXT non possono essere proxati. Per il sito esistente verifica la compatibilità indicata dal suo hosting prima di attivare il proxy; il CNAME del laboratorio, creato più avanti dal tunnel, sarà invece **Proxied**. [Limiti del proxy DNS](https://developers.cloudflare.com/dns/proxy-status/limitations/).

### Cambiare i nameserver presso il registrar

Cloudflare mostra i nameserver assegnati alla tua zona durante l’attivazione; puoi ritrovarli nella pagina **Overview** del dominio. Copia quelli indicati per la tua zona, senza riutilizzare valori trovati in un’altra guida.

Prima della sostituzione controlla DNSSEC. Questa protezione firma i dati DNS; il record DS presso il registrar collega la delega alle chiavi della zona. Il TTL indica quanto un dato può restare in cache.

Se DNSSEC è attivo, completa prima questo passaggio con le [istruzioni del tuo registrar](https://developers.cloudflare.com/dns/dnssec/#disable-dnssec):

1. Prima delle modifiche, recupera e annota il TTL originario del DS e quello della delega NS precedente. Se il pannello non li espone, chiedili al registrar o al provider DNS: il TTL residuo restituito da un resolver non basta per stimare l’intera attesa.
2. Rimuovi il vecchio DS e fatti confermare che la rimozione è stata pubblicata nella zona superiore. Conta da quel momento l’intero TTL annotato del DS, non dal clic nel pannello.
3. Cambia nameserver solo dopo quell’attesa e la conferma dell’assenza del vecchio DS. Dopo la pubblicazione della nuova delega, attendi anche il TTL dei precedenti NS prima di aggiungere il nuovo DS.

Se non riesci a ottenere tempi e conferme, fai completare la migrazione dal provider prima di proseguire. Un DS ancora legato alle vecchie chiavi può rendere il dominio irraggiungibile.

Apri quindi il pannello del registrar e cerca la gestione **Nameserver**, spesso nelle impostazioni del dominio. Sostituisci i precedenti con quelli assegnati da Cloudflare e salva. Nel full setup ordinario non mantenere un insieme misto di nameserver vecchi e nuovi. I nomi dei menu dipendono dal registrar; Cloudflare raccoglie le [istruzioni per i provider più comuni](https://developers.cloudflare.com/dns/nameservers/update-nameservers/).

Non devi aggiungere un record A verso il Raspberry per effettuare questo passaggio. Non devi neppure inserire `1.1.1.1`: è un resolver pubblico usato dai dispositivi per le ricerche DNS, non il nameserver autorevole assegnato al tuo dominio.

Torna su Cloudflare e attendi lo stato **Active**. **Pending Nameserver Update** indica che la delega non è ancora stata verificata. Dopo il cambio e la scadenza del TTL dei precedenti nameserver, abilita DNSSEC in Cloudflare e pubblica il nuovo DS secondo la procedura del registrar. Questo percorso di migrazione comporta un intervallo senza protezione DNSSEC. [Sequenza DNSSEC](https://developers.cloudflare.com/dns/dnssec/).

### Verificare la delega e i servizi esistenti

Dal terminale dei controlli sul Raspberry:

```bash
dig +short NS example.com
dig +short MX example.com
dig +short TXT example.com
dig +short DS example.com
```

Confronta i nameserver con quelli assegnati da Cloudflare e i record di posta con l’inventario iniziale. Cache e TTL possono ritardare i risultati. Le query aiutano il confronto, ma non costituiscono da sole una validazione della catena DNSSEC.

Verifica anche che il sito si apra e che la posta continui a funzionare. Solo dopo passa al laboratorio. In seguito, i record del dominio si gestiranno in **DNS → Records** su Cloudflare: modificare la vecchia zona presso il registrar non aggiornerà più il DNS autorevole.

Non creare ora un record con l’IP di casa. Più avanti, la route del tunnel collegherà `lab.example.com` al connettore e la dashboard creerà il relativo record DNS.

## Avviare un’origine che possiamo riconoscere

Prima del tunnel, facciamo funzionare il servizio locale. Controlla che la porta scelta sia libera:

```bash
ss -lnt 'sport = :18080'
```

Non deve comparire un listener. Se la porta è occupata, individua il servizio esistente e scegli un’altra porta, aggiornando tutti i riferimenti della guida.

Nel primo terminale crea una directory nuova. Il blocco si ferma se esiste già, così non sovrascrive una tua applicazione:

```bash
(
  set -eu
  mkdir "$HOME/tunnel-lab"
  cat > "$HOME/tunnel-lab/index.html" <<'HTML'
<!doctype html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <title>Laboratorio Cloudflare Tunnel</title>
</head>
<body>
  <h1>Origine del laboratorio raggiunta</h1>
  <p>Questa pagina contiene soltanto dati dimostrativi.</p>
</body>
</html>
HTML
)
```

Hai scritto tu `index.html`; nessun installer lo genera. Avvia il server e lascia aperto il terminale:

```bash
python3 -m http.server 18080 \
  --bind 127.0.0.1 \
  --directory "$HOME/tunnel-lab"
```

Da un altro terminale sullo stesso Raspberry:

```bash
curl --fail --show-error http://127.0.0.1:18080/
ss -lnt 'sport = :18080'
```

Devi ricevere l’HTML con **Origine del laboratorio raggiunta** e vedere l’ascolto su `127.0.0.1:18080`. `0.0.0.0` o `[::]` indicherebbero un’esposizione diversa da quella progettata.

La directory web deve contenere soltanto la pagina dimostrativa: niente token, backup, file `.env` o collegamenti simbolici a directory private. Il [server HTTP di Python](https://docs.python.org/3.13/library/http.server.html) è adatto a questa prova, non a un’applicazione di produzione.

## Installare cloudflared dal repository ufficiale

La guida usa un tunnel **gestito da remoto**: route e impostazioni stanno nella dashboard Cloudflare. Il Raspberry conserva il programma e il token necessario a collegarsi. Non dobbiamo creare un file locale `config.yml`, eseguire `tunnel login` o generare le credenziali di un tunnel gestito localmente.

Controlla prima se il programma e il repository sono già presenti:

```bash
command -v cloudflared || true
grep -R "pkg.cloudflare.com" \
  /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true
```

Se trovi una configurazione esistente, riusala e verifica che sia il repository stabile corrente. Non aggiungerne un duplicato. Per un’installazione nuova, il blocco seguente crea i due file indicati e si ferma se uno esiste già:

```bash
(
  set -eu
  set -o pipefail
  test ! -e /usr/share/keyrings/cloudflare-main.gpg
  test ! -e /etc/apt/sources.list.d/cloudflared.sources

  LAB_KEY=$(mktemp)
  trap 'rm -f -- "$LAB_KEY"' EXIT
  curl --fail --show-error --silent \
    --output "$LAB_KEY" \
    https://pkg.cloudflare.com/cloudflare-main.gpg
  sudo install -d -m 0755 /usr/share/keyrings
  sudo install -m 0644 "$LAB_KEY" /usr/share/keyrings/cloudflare-main.gpg

  sudo tee /etc/apt/sources.list.d/cloudflared.sources >/dev/null <<'APT'
Types: deb
URIs: https://pkg.cloudflare.com/cloudflared
Suites: any
Components: main
Signed-By: /usr/share/keyrings/cloudflare-main.gpg
APT

  sudo apt-get update
  sudo apt-get install cloudflared
)
```

La chiave e la definizione del repository riguardano APT, non il tunnel. Usa la [chiave pubblicata oggi dal repository ufficiale](https://pkg.cloudflare.com/index.html), evitando vecchie istruzioni con `apt-key`.

Verifica il risultato:

```bash
command -v cloudflared
cloudflared --version
cloudflared tunnel run --help
```

I comandi successivi assumono `/usr/bin/cloudflared`, il percorso del pacchetto. Se il tuo è diverso, correggi anche le unità `systemd`. L’opzione `--token-file` richiede la versione **2025.4.0 o successiva**: deve comparire nell’help. Annota la versione effettiva del tuo laboratorio, anziché dedurla dal giorno in cui hai seguito la guida.

## Configurare Access prima di pubblicare la route

Apri **Zero Trust** dall’account Cloudflare. Se non hai ancora un’organizzazione, completa la configurazione iniziale e scegli il nome del team. Quel nome forma il dominio `NOME_TEAM.cloudflareaccess.com`; non è il tuo sottodominio applicativo.

Per il laboratorio scegliamo il codice monouso via email. Nelle nuove organizzazioni va aggiunto esplicitamente in **Integrations → Identity providers → Add new identity provider → One-time PIN**. Chi controlla quella casella può ricevere i codici: proteggila con una password unica e l’autenticazione a più fattori del provider email. [Configurazione One-time PIN](https://developers.cloudflare.com/cloudflare-one/integrations/identity-providers/one-time-pin/).

Ora crea l’applicazione dalla sezione **Zero Trust → Access controls → Applications**. Nella dashboard corrente:

1. Premi **Create new application**, scegli **Self-hosted and private** e prosegui con **Continue with Self-hosted and private**.
2. In **Application details → Destinations**, configura il public hostname. Con i campi separati inserisci `lab` in **Subdomain** e seleziona il dominio attivo. Se usi **Switch to custom input**, inserisci invece l’hostname completo `lab.example.com`, come nella schermata.
3. Lascia vuoto **Path**: la protezione deve coprire l’intero hostname. Il campo del nome non crea da solo un server o un tunnel: dichiara quale destinazione proteggerà Access.

![Campo hostname dell’applicazione Cloudflare Access con un dominio dimostrativo.](https://cdn.sanity.io/images/xrqkwry7/production/c9549eca198f7cee83cd4ef646bdec7fae9d27f3-1040x310.jpg?w=1400&fit=max&auto=format)

Applicazione Access: l’hostname completo in modalità custom input. Il nome mostrato è dimostrativo. Usa un hostname appartenente al dominio già attivo nel tuo account Cloudflare; il record del tunnel verrà creato più avanti con la route.

Scorri fino a **Access policies** e premi **Create new policy**. In **Include**, scegli il selettore **Emails** e inserisci l’indirizzo preciso da autorizzare. Dai alla policy un nome riconoscibile, per esempio **Tunnel lab browser**, e mantieni l’azione **Allow**.

Una regola Include ammette chi soddisfa quel criterio. Qui l’elenco deve contenere le sole email previste; il placeholder della schermata non è un indirizzo da autorizzare. Le regole **Require** aggiungono condizioni da soddisfare insieme, mentre **Exclude** esclude identità o condizioni specifiche. Per questo primo laboratorio basta l’elenco esplicito delle email.

![Regola Cloudflare Access con selettore Emails e azione Allow.](https://cdn.sanity.io/images/xrqkwry7/production/c9ed50dcb88987fdbdebed1b4aa89c60da19ff69-1280x470.jpg?w=1400&fit=max&auto=format)

Regola Access: selettore Emails e azione Allow. Il campo email e il nome della policy sono da compilare; qui non compaiono identità reali.

Lascia **Policy session duration** su **Same as application session duration** e salva la policy con **Save policy**. Controlla che risulti associata all’applicazione.

Nella sezione **Authentication → Identity**, scegli **One-time PIN**. Se è attivo **Accept all available identity providers**, disattivalo per selezionare esplicitamente questo metodo nel laboratorio. Il PIN via email è un metodo di login; è la policy Emails a stabilire quali indirizzi sono ammessi. Avere accesso a una casella qualsiasi non deve bastare.

Infine, in **Details**, imposta **Name** a **Tunnel lab** e **Session Duration** a un’ora per l’esempio. Rileggi destinazione, policy e metodo di autenticazione, poi salva l’applicazione con **Create**. Le schermate mostrano i campi del modulo, non una configurazione già attiva.

La durata scelta limita la validità del token per l’applicazione. Alla scadenza Access ricontrolla le policy; se la sessione globale di identità è ancora valida, può rinnovare l’accesso senza chiedere un nuovo codice. La [gestione delle sessioni](https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/session-management/) distingue questi due tempi.

Per questo risultato non usare una regola **Bypass** o un’autorizzazione **Everyone**. Controlla anche che non ci siano altre applicazioni o regole già associate al nome scelto.

L’[applicazione Access va preparata prima della route pubblica](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/self-hosted-public-app/): salvare la route rende raggiungibile l’endpoint. Access e la route devono riferirsi esattamente allo stesso hostname.

Recupera inoltre due valori per la validazione nel connettore:

- Il **team name**, visibile nelle impostazioni Zero Trust: il prefisso di `NOME_TEAM.cloudflareaccess.com`.
- L’**Application Audience (AUD) Tag**, in **Access controls → Applications → Configure → Additional settings** per l’app appena creata.

L’AUD identifica l’applicazione per cui il token Access è valido. Non è il token del tunnel.

## Creare il tunnel e conservare il suo token

Nella dashboard Cloudflare apri **Networking → Tunnels → Create a tunnel** e crea un tunnel `cloudflared` dedicato, per esempio **raspberry-lab**.

Il pannello mostra le istruzioni di installazione e il token del connettore. Conserva quel token nel tuo gestore dei segreti. In questa procedura <strong>non eseguire anche `cloudflared service install`</strong>: prima avviamo il connettore in foreground; più avanti creeremo un servizio dedicato.

Nel secondo terminale Bash sul Raspberry, salva soltanto il token con input nascosto:

```bash
(
  set -eu
  set +x
  umask 077
  mkdir -m 0700 "$HOME/tunnel-lab-secrets"
  read -r -s -p "Token del tunnel di laboratorio: " LAB_TUNNEL_TOKEN
  printf '\n'
  test -n "$LAB_TUNNEL_TOKEN"
  printf '%s' "$LAB_TUNNEL_TOKEN" \
    > "$HOME/tunnel-lab-secrets/tunnel.token"
  unset LAB_TUNNEL_TOKEN
)
```

Il file contiene il solo token, senza `TOKEN=`, virgolette o JSON. È fuori dalla directory servita da Python e viene creato con permessi ristretti. Non inserire il segreto direttamente nel comando, in Git o in uno screenshot.

Avvia il connettore:

```bash
/usr/bin/cloudflared tunnel --no-autoupdate run \
  --token-file "$HOME/tunnel-lab-secrets/tunnel.token"
```

Lascia aperto il terminale e verifica nella dashboard che il tunnel risulti **Healthy**. Questo conferma la connessione al connettore; il servizio web non è ancora stato verificato attraverso il tunnel. Il [parametro token-file](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/run-parameters/#token-file) evita il segreto negli argomenti del processo.

## Associare hostname, origine e validazione Access

Dalla dashboard apri **Networking → Tunnels**, seleziona il tunnel dedicato al laboratorio e vai in **Routes → Add route → Published application**. Questo passaggio associa un nome pubblico al servizio locale. Nel modulo **Add published application** imposta:

_Associare hostname, origine e validazione Access_

| Campo | Valore del laboratorio |
| --- | --- |
| Subdomain | `lab` |
| Domain | Il tuo dominio attivo, rappresentato qui da `example.com` |
| Path | Vuoto |
| Service URL | `http://127.0.0.1:18080` |

![Campi hostname pubblico e Service URL di una route Cloudflare Tunnel.](https://cdn.sanity.io/images/xrqkwry7/production/c834d1554ff228710223894c4800be8c4025b74d-672x605.jpg?w=1400&fit=max&auto=format)

Route del tunnel: hostname pubblico e Service URL sono due campi distinti. I valori grigi sono placeholder dell’interfaccia; per questa guida il Service URL indica il server HTTP di loopback sulla porta 18080, come riportato nel testo.

Il campo **Domain** può essere precompilato: verifica che sia la zona che vuoi utilizzare. Il **Service URL** indica ciò che il connettore deve raggiungere sul Raspberry, non l’indirizzo pubblico che aprirai nel browser.

Nelle **Additional application settings**, abilita **Protect with Access** e inserisci team name e AUD dell’applicazione creata prima. Il controllo deve essere richiesto. In questo modo `cloudflared` verifica firma, emittente e destinatario del JWT Access prima di inoltrare la richiesta. Non occorre aggiungere un file YAML locale per queste impostazioni remote. [Parametri Access dell’origine](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/origin-parameters/#access).

Dopo avere verificato hostname, origine e validazione Access, conferma con **Add route**. Da questo momento l’endpoint può essere raggiunto: per questo l’applicazione Access deve essere già pronta.

Torna al dominio in **Domains** e apri **DNS → Records**. Nel setup completo la dashboard configura il record collegato alla route:

_Associare hostname, origine e validazione Access_

| Tipo | Nome | Destinazione | Stato |
| --- | --- | --- | --- |
| CNAME | `lab` | `ID_DEL_TUNNEL.cfargotunnel.com` | Proxied |

Usa l’identificatore del tuo tunnel, generato da Cloudflare. Non inserire l’IP privato del Raspberry o l’IP pubblico di casa. Se il nome ha già un record incompatibile, risolvi quel conflitto; non aggiungere una seconda definizione alla cieca. [Creazione del tunnel e della route](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/).

`127.0.0.1` funziona perché il connettore e Python girano sullo stesso host. In un container, `localhost` indicherebbe il container stesso: per collegare due container servirebbe una rete condivisa e il nome del servizio. Qui evitiamo Docker e un reverse proxy perché non servono a dimostrare questo collegamento.

## Verificare il percorso completo

Dal terminale dei controlli sul Raspberry, con il vero hostname del laboratorio:

```bash
dig +short lab.example.com A
dig +short lab.example.com AAAA
curl --silent --show-error --max-time 15 \
  --dump-header - --output /dev/null \
  https://lab.example.com/
```

Con il proxy attivo, DNS può restituire indirizzi Cloudflare invece di mostrare il target CNAME del tunnel. Il target si controlla nella zona DNS.

La richiesta `curl` non include credenziali e non segue i redirect: deve incontrare Access, non ottenere la pagina del laboratorio. Un `302` verso il login è un risultato previsto per il percorso browser, non una prova di guasto.

Apri poi una finestra privata del browser, visita l’hostname, inserisci l’email autorizzata e usa il codice ricevuto. Il risultato positivo è la frase **Origine del laboratorio raggiunta** dopo il login. Vedere soltanto la schermata Access dimostra che hai raggiunto il controllo di accesso, non il backend.

Per provare un’identità esclusa, chiudi tutte le finestre private e aprine una nuova, oppure usa un profilo browser separato senza sessioni Access. Una seconda scheda privata può condividere il login già riuscito e falsare il controllo.

Completa queste prove sul laboratorio:

_Verificare il percorso completo_

| Prova | Risultato da verificare |
| --- | --- |
| `curl` locale su `127.0.0.1:18080` | HTML del laboratorio |
| Browser senza sessione | Login o rifiuto, senza contenuto dell’origine |
| Email autorizzata e codice valido | Pagina del laboratorio |
| Email non inclusa nella policy | Nessun accesso alla pagina |
| Listener locale con `ss` | Solo `127.0.0.1:18080` |
| Da un altro dispositivo LAN verso IP del Raspberry e porta 18080 | Nessuna risposta dell’origine su quella porta |

Per l’ultimo controllo usa l’indirizzo LAN del Raspberry di laboratorio. Il bind loopback riguarda questo server: non certifica l’esposizione di ogni altro servizio del Raspberry.

Con One-time PIN, il messaggio che annuncia l’invio del codice può apparire anche per un indirizzo escluso. Non aspettarti quindi sempre un `403` immediato: il criterio è che quell’identità non ottenga il contenuto.

Esegui il controllo pubblico anche attraverso una connessione indipendente, per esempio il browser di un telefono sulla rete mobile. Poi prova due guasti separati: ferma Python con Ctrl+C, controlla l’errore e riavvialo con lo stesso comando; solo dopo ferma `cloudflared` e ripeti la prova. Questa sequenza permette di distinguere un’origine assente da un tunnel scollegato.

Prima di proseguire, riavvia anche `cloudflared` con il comando `tunnel --no-autoupdate run --token-file` già usato. Attendi lo stato **Healthy** e verifica di nuovo la frase del laboratorio nel browser autenticato.

## Estensione: autorizzare uno script

Un browser usa un’identità personale; uno script può usare un **Access service token**, composto da Client ID e Client Secret. Il token che hai salvato sul Raspberry serve invece ad avviare il tunnel. Sono credenziali diverse.

In **Access controls → Service credentials → Service Tokens**, crea un token dedicato al laboratorio e scegli una durata. Salva il secret quando viene mostrato. Nell’applicazione **Tunnel lab** aggiungi una policy separata con azione **Service Auth**, includendo quello specifico service token. Mantieni la policy personale per il browser.

Puoi eseguire la prova dal terminale dei controlli sul Raspberry. Per usare un altro computer servono Bash e `curl` già disponibili; il browser da solo non esegue questo blocco.

Il client invia `CF-Access-Client-Id` e `CF-Access-Client-Secret` a ogni richiesta. Questo blocco Bash nasconde il Client Secret durante l’inserimento e passa gli header a `curl` attraverso standard input:

```bash
(
  set -eu
  set +x
  read -r -p "Hostname del laboratorio: " LAB_HOST
  read -r -p "Access Client ID: " LAB_ACCESS_ID
  read -r -s -p "Access Client Secret: " LAB_ACCESS_SECRET
  printf '\n'
  test -n "$LAB_HOST"
  test -n "$LAB_ACCESS_ID"
  test -n "$LAB_ACCESS_SECRET"

  printf 'header = "CF-Access-Client-Id: %s"\nheader = "CF-Access-Client-Secret: %s"\n' \
    "$LAB_ACCESS_ID" "$LAB_ACCESS_SECRET" \
    | curl --config - --fail --silent --show-error --max-time 15 \
        "https://$LAB_HOST/"

  unset LAB_ACCESS_ID LAB_ACCESS_SECRET
)
```

Inserisci nel primo campo solo l’hostname, senza `https://` o percorso. Non usare modalità verbose o shell tracing con credenziali reali. Per una CI, affidale al gestore dei segreti della piattaforma.

Con credenziali valide devi ottenere la stessa pagina; con un secret errato non devi ottenerla. La modalità **strict service token authentication**, obbligatoria per le organizzazioni create dal 5 ottobre 2026, restituisce `401` o `403` sui fallimenti invece di rimandare al login e non rilascia il cookie browser. Nelle organizzazioni precedenti il comportamento dipende dall’impostazione. [Service token e modalità strict](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/).

Mantieni **Protect with Access** anche per questo percorso: il JWT inoltrato al connettore può attestare l’autenticazione tramite service token, senza richiedere un cookie browser. [Token applicativi Access](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/application-token/).

La pagina statica non ha un secondo login. Se la sostituisci con un’applicazione che richiede un proprio token o una sessione, quel controllo resta separato: superare Access non assegna automaticamente i permessi applicativi.

## Estensione: riavviare automaticamente il laboratorio

Finora la chiusura dei terminali ferma i processi. Per mantenere il laboratorio dopo il logout o un riavvio servono **due servizi**, uno per l’origine e uno per il tunnel.

La configurazione seguente usa due account senza login e directory dedicate. Rimane un laboratorio persistente: rendere persistente `http.server` non lo trasforma in un server di produzione.

Prima verifica che questi nomi e percorsi siano liberi. Se trovi risorse esistenti, fermati e scegli nomi diversi in tutti i comandi:

```bash
getent passwd tunnel-lab-web || true
getent passwd tunnel-lab-connector || true
getent group tunnel-lab-web || true
getent group tunnel-lab-connector || true
sudo ls -ld /srv/tunnel-lab /etc/cloudflared-lab \
  /etc/systemd/system/tunnel-lab-web.service \
  /etc/systemd/system/tunnel-lab-connector.service 2>/dev/null || true
```

Crea gli account e copia soltanto i file necessari:

```bash
sudo useradd --system --user-group --home-dir /nonexistent \
  --shell /usr/sbin/nologin tunnel-lab-web
sudo useradd --system --user-group --home-dir /nonexistent \
  --shell /usr/sbin/nologin tunnel-lab-connector

sudo install -d -o root -g root -m 0755 /srv/tunnel-lab
sudo install -o root -g root -m 0644 \
  "$HOME/tunnel-lab/index.html" /srv/tunnel-lab/index.html

sudo install -d -o root -g tunnel-lab-connector -m 0750 \
  /etc/cloudflared-lab
sudo install -o root -g tunnel-lab-connector -m 0640 \
  "$HOME/tunnel-lab-secrets/tunnel.token" \
  /etc/cloudflared-lab/tunnel.token
```

L’account web può leggere la pagina, ma non il token del connettore. Quest’ultimo è leggibile dal gruppo del connettore e modificabile da root.

Scrivi la prima unità completa:

```bash
sudo tee /etc/systemd/system/tunnel-lab-web.service >/dev/null <<'UNIT'
[Unit]
Description=Tunnel lab demonstration website

[Service]
Type=simple
User=tunnel-lab-web
Group=tunnel-lab-web
ExecStart=/usr/bin/python3 -m http.server 18080 --bind 127.0.0.1 --directory /srv/tunnel-lab
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
UNIT
```

Poi il connettore:

```bash
sudo tee /etc/systemd/system/tunnel-lab-connector.service >/dev/null <<'UNIT'
[Unit]
Description=Cloudflare Tunnel lab connector
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=tunnel-lab-connector
Group=tunnel-lab-connector
ExecStart=/usr/bin/cloudflared tunnel --no-autoupdate run --token-file /etc/cloudflared-lab/tunnel.token
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
UNIT
```

Sono file scritti da te, non generati da Cloudflare. I percorsi assoluti evitano di dipendere dalla directory della shell; `ProtectHome=true` spiega perché i servizi usano `/srv` e `/etc` invece dei file sotto la tua home. Le restrizioni sul filesystem riducono le scritture disponibili ai processi. [Ambiente di esecuzione systemd](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html).

Ferma con Ctrl+C i due processi ancora in foreground. Prima valida le unità:

```bash
sudo systemd-analyze verify \
  /etc/systemd/system/tunnel-lab-web.service \
  /etc/systemd/system/tunnel-lab-connector.service
```

Se la verifica segnala errori relativi alle due unità, correggili e ripetila. Solo dopo una verifica riuscita avvia i servizi:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now tunnel-lab-web.service tunnel-lab-connector.service
systemctl is-active tunnel-lab-web.service tunnel-lab-connector.service
systemctl is-enabled tunnel-lab-web.service tunnel-lab-connector.service
```

I risultati attesi sono `active` e `enabled` per entrambi. Ripeti il controllo locale e quello pubblico con login.

Per leggere lo stato e gli ultimi messaggi:

```bash
sudo systemctl status tunnel-lab-web.service tunnel-lab-connector.service --no-pager
sudo journalctl -u tunnel-lab-web.service -u tunnel-lab-connector.service \
  -n 80 --no-pager
```

`Restart=on-failure` permette a systemd di riavviare un processo terminato con errore; `enable` lo configura per il boot. Non significa che un comando esplicito di stop debba essere annullato, né garantisce disponibilità se mancano alimentazione o connessione. [Comportamento dei servizi systemd](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html).

Su un laboratorio che puoi interrompere, riavvia il Raspberry con `sudo reboot`. Dopo esserti ricollegato, controlla di nuovo stato, listener, risposta locale e accesso pubblico. Il test dopo reboot è parte del risultato promesso da questa estensione.

## Leggere gli errori senza cambiare tutto insieme

_Leggere gli errori senza cambiare tutto insieme_

| Sintomo | Primo controllo utile |
| --- | --- |
| Il nome non risolve | Delega attiva, record nella zona autorevole e hostname digitato |
| Redirect `302` verso Access | È richiesto il login; non trattarlo come risposta API riuscita |
| Nessun accesso dopo il login | Policy, identità scelta e durata della sessione |
| Rifiuto del connettore dopo Access | Team name, AUD e raggiungibilità delle chiavi di validazione |
| `502` relativo all’origine Tunnel | Python attivo, porta, protocollo e risposta locale |
| Errore Cloudflare `1033` | Connettore disponibile, sua rete in uscita e tunnel selezionato |
| Service token respinto | Client ID/Secret, scadenza, policy Service Auth e modalità strict |
| Funziona fino alla chiusura della shell | Il processo è ancora in foreground o manca uno dei due servizi |
| Funziona sull’host, non nel container | Il `localhost` del container non è quello dell’host |

Lo stato **Healthy** del tunnel e un `200` restituito dal servizio locale sono prove di due tratti diversi. Per verificare l’intero percorso serve la risposta corretta all’hostname pubblico dopo l’autenticazione. Anche un singolo codice HTTP va letto insieme alla pagina restituita e ai log del componente. [Diagnostica ufficiale Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/troubleshoot-tunnels/common-errors/).

## Manutenzione e passaggio a un’applicazione reale

Per aggiornare il pacchetto installato da APT, in una finestra in cui puoi interrompere il laboratorio:

```bash
sudo apt-get update
sudo apt-get install --only-upgrade cloudflared
cloudflared --version
```

Se hai configurato l’estensione systemd, riavvia il connettore con `sudo systemctl restart tunnel-lab-connector.service`. Se lo esegui ancora nel terminale, fermalo con Ctrl+C e rilancialo con il comando `tunnel --no-autoupdate run --token-file` già usato. In entrambi i casi attendi **Healthy** e ripeti i controlli di accesso.

`--no-autoupdate` rende esplicito che il ciclo di aggiornamento è gestito attraverso il pacchetto e il servizio.

La rotazione del token del tunnel e la scadenza di un service token Access sono operazioni diverse. Dopo aver sostituito il token del connettore nel suo file, riavvia il connettore con la modalità scelta, servizio systemd o processo nel terminale, perché lo rilegga. La [rotazione del tunnel token](https://developers.cloudflare.com/tunnel/reference/tunnel-tokens/) non chiude da sola tutte le connessioni già stabilite: considera tutte le repliche quando pianifichi la sostituzione.

Per un servizio reale, sostituisci Python con un’applicazione mantenuta, definisci aggiornamenti, backup dei suoi dati e una prova di ripristino. Verifica le autorizzazioni dell’applicazione oltre ad Access. Se pubblichi porte Docker, controlla l’esposizione effettiva: una porta pubblicata e le sole regole del firewall host possono dare un quadro diverso da quello che immagini.

Un Raspberry domestico dipende da alimentazione, storage e connessione. Per un servizio con obblighi di disponibilità, confronta questi costi operativi con un hosting gestito prima di scegliere dove farlo vivere.

## Quando conviene un accesso privato

Il tutorial crea un hostname pubblico il cui contenuto è protetto. Per amministrare risorse riservate da pochi dispositivi controllati può essere più adatto un percorso di rete privato.

_Quando conviene un accesso privato_

| Scelta | Chi raggiunge il servizio | Cosa richiede al client |
| --- | --- | --- |
| Cloudflare Tunnel con Access | Persone o automazioni ammesse all’hostname pubblico | Browser o credenziali macchina |
| Tailscale Serve | Utenti e dispositivi della tailnet secondo le policy | Accesso autorizzato alla tailnet |
| Twingate | Utenti autorizzati alla specifica Resource | Client e percorso tramite Connector |
| Cloudflare private routing | Client ammessi alle route private configurate | Cloudflare One Client e policy coerenti |

[Tailscale Serve](https://tailscale.com/docs/features/tailscale-serve) e [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel) rispondono a esigenze diverse: Funnel pubblica verso Internet, Serve serve la tailnet. [Twingate](https://www.twingate.com/docs/how-twingate-works) organizza l’accesso attorno a risorse e autorizzazioni; il suo Connector deve poter raggiungere l’origine. Queste alternative richiedono una configurazione propria e non si ottengono cambiando soltanto una policy Access.

Se migri a un accesso privato, controlla che la precedente route pubblica non resti aperta per errore. Per un contenuto deliberatamente pubblico, invece, Tunnel può essere usato senza Access: è una scelta adatta a un sito pensato per visitatori anonimi, non una soluzione a un errore di autenticazione del laboratorio.

## Rimuovere il laboratorio senza toccare gli altri servizi

Dal pannello Cloudflare rimuovi la published application route del laboratorio e verifica la rimozione del relativo record DNS. Fallo prima di cancellare l’applicazione Access. Poi elimina l’app Access dedicata, revoca l’eventuale service token e cancella il tunnel quando non ha altri utilizzi. Non rimuovere policy riutilizzate da altre applicazioni o la zona DNS del dominio.

Se hai usato solo i processi in foreground, fermali con Ctrl+C. Se hai completato l’estensione systemd:

```bash
sudo systemctl disable --now tunnel-lab-connector.service tunnel-lab-web.service
sudo rm -- \
  /etc/systemd/system/tunnel-lab-connector.service \
  /etc/systemd/system/tunnel-lab-web.service
sudo systemctl daemon-reload

sudo rm -- /etc/cloudflared-lab/tunnel.token /srv/tunnel-lab/index.html
sudo rmdir /etc/cloudflared-lab /srv/tunnel-lab
sudo userdel tunnel-lab-connector
sudo userdel tunnel-lab-web
```

Controlla con `getent group` se sono rimasti i due gruppi creati per il laboratorio e, soltanto in quel caso, rimuovili con `sudo groupdel`. Non eseguire questi comandi per account preesistenti.

Elimina infine i due file iniziali e le loro directory:

```bash
rm -- "$HOME/tunnel-lab/index.html" "$HOME/tunnel-lab-secrets/tunnel.token"
rmdir "$HOME/tunnel-lab" "$HOME/tunnel-lab-secrets"
ss -lnt 'sport = :18080'
```

`rmdir` si ferma se trova altro contenuto: esaminalo prima di cancellarlo. Non disinstallare Python, gli strumenti di sistema o `cloudflared` se servono ad altro. Su una macchina dedicata puoi rimuovere anche il pacchetto e i file APT aggiunti dalla guida, dopo aver verificato che nessun altro tunnel li utilizzi.
