Expondo um Hub ESP32 na Internet com Cloudflare Tunnel e ntfy.sh

Eletrogate 21 de novembro de 2025

Introdução

Em projetos de IoT baseados em ESP32, é comum precisar de um Hub central — um servidor local responsável por receber dados, exibir dashboards e coordenar a comunicação entre diversos módulos sensores e atuadores.
Entretanto, quando esse Hub está atrás de um roteador doméstico, surge uma limitação: como acessá-lo remotamente pela Internet sem depender de IP fixo ou configurações complexas de NAT/port forwarding?

Soluções tradicionais como DDNS ou VPNs podem funcionar, mas envolvem custos, dependências externas ou configurações avançadas.
Com o Cloudflare Tunnel, e em particular o Quick Tunnel, torna-se possível publicar o servidor local com pouco esforço, sem expor a rede doméstica e sem exigir autenticação prévia com a conta Cloudflare.
Cada inicialização gera automaticamente uma URL pública temporária no domínio trycloudflare.com.

Para tornar essa arquitetura realmente prática, o projeto acrescenta um segundo componente essencial: o serviço de notificação ntfy.sh, que envia a nova URL pública diretamente ao usuário, sempre que o sistema é reiniciado.


Motivação

A motivação deste trabalho surgiu da necessidade de monitorar e controlar múltiplas estações ESP32 de forma remota, utilizando um Hub central com interface web local (via Nginx e HTML).
Nos testes iniciais, verificou-se que o Quick Tunnel atende perfeitamente para uso doméstico, laboratorial e educacional, porém sua principal limitação é a não persistência da URL pública — ela muda a cada reinicialização.

A solução foi criar um serviço automatizado (systemd) que:

  1. Aguarda o túnel ser criado pelo cloudflared;
  2. Extrai a nova URL do log do serviço;
  3. Testa a acessibilidade do endpoint local;
  4. Envia automaticamente uma notificação ao tópico do ntfy.sh configurado, contendo a nova URL de acesso público.

Assim, o sistema torna-se autônomo e resiliente:
cada vez que o servidor Linux ou Raspberry Pi é ligado, a URL é recriada, validada e comunicada ao administrador — permitindo acesso remoto imediato ao Hub e aos dashboards ESP32 sem intervenção manual.


Pré-requisitos

Antes de iniciar a configuração do túnel e das notificações automáticas, é importante garantir que o ambiente de base esteja pronto e funcional.
A seguir, são listados os componentes de hardware e software necessários, bem como as etapas iniciais para acesso remoto via mDNS — um recurso que facilita o uso do Putty a partir de outro computador na mesma rede local.

  1. Ambiente Operacional

O projeto foi testado em:

  • Linux (Ubuntu 24.04.02 LTS ou superior) — executando num notebook com 8Gb RAM;
  • Raspberry Pi OS 64-bits— executando em Raspberry Pi 4 B com 4Gb RAM.

Ambos oferecem suporte nativo ao systemd, essencial para a automação dos serviços cloudflared e cf-url-notify.

Partiremos do princípio de que o leitor já possui:

  • O sistema operacional instalado e configurado;
  • Acesso à Internet;
  • E acesso local via terminal ou SSH.

Para referência, tutoriais de instalação do Raspberry Pi OS podem ser encontrados no site oficial:
🔗 https://www.raspberrypi.com/software/

  1. Acesso remoto facilitado via mDNS

Durante o desenvolvimento, o acesso remoto foi realizado a partir de um computador Windows, utilizando o Putty.
Para evitar depender de endereços IP dinâmicos na LAN, configuramos o serviço mDNS (Multicast DNS) no Linux, o que permite o acesso pelo nome do host seguido de .local.
Por exemplo:

ssh dailton@domlinux.local

Instalação e ativação do mDNS 

No Linux, execute:

sudo apt update
sudo apt install avahi-daemon avahi-utils -y
sudo systemctl enable --now avahi-daemon

No Raspberry PI, na geração da imagem pelo utilitário oficial, é possível definir o hostname e habilitar o SSH, eliminando dois passos no procedimento (instalar mDNS e habilitar SSH). Veja o link a seguir:

https://www.raspberrypi.com/documentation/computers/getting-started.html

Verifique se o serviço está ativo:

systemctl status avahi-daemon

Se tudo estiver correto, o comando deve indicar active (running).

Configure o hostname desejado

sudo hostnamectl set-hostname <hostname>

Substitua <hostname>pelo nome de sua escolha (por exemplo, meu-hub ou raspberrypi).

Atualize o arquivo /etc/hosts (opcional, mas recomendado)

sudo nano /etc/hosts

Verifique se existe uma linha semelhante a:

127.0.1.1 <hostname>

Caso não exista, adicione-a logo após a linha 127.0.0.1 localhost.

Reinicie o serviço Avahi para aplicar as alterações:

sudo systemctl restart avahi-daemon

Teste o acesso pelo Windows com PuTTY ou terminal:

ssh <user>@<hostname>.local

Se tudo estiver correto, o PuTTY (ou qualquer cliente SSH) resolverá automaticamente o nome <hostname>.local via mDNS, conectando ao seu Linux sem precisar saber o IP.

💡 Dica:
O mDNS também funciona entre máquinas Linux e macOS — basta que estejam na mesma rede local e com o Avahi (Linux) ou Bonjour (macOS/Windows) instalados e ativos.

  1. Pacotes essenciais

Além do avahi-daemon, serão necessários os seguintes pacotes:

sudo apt install nginx curl systemd net-tools -y

Esses componentes cobrem:

  • nginx — servidor web local (Hub ESP32);
  • curl — ferramenta de requisições HTTP usada nos testes e notificações;
  • systemd — responsável por gerenciar serviços e timers;
  • net-tools — utilitários de rede como ss, ifconfig, etc.
  1. Configuração no ntfy.sh

Para o envio das notificações, é necessário ter um tópico público ou privado criado no serviço ntfy.sh.
O projeto usará o tópico de exemplo:

https://ntfy.sh/dom_07c2_07e9_alerts

Esse endereço pode ser acessado diretamente no navegador ou pelo aplicativo ntfy no celular, onde as notificações aparecerão automaticamente sempre que o túnel for recriado.


Arquitetura do Sistema

A figura a seguir apresenta a arquitetura geral da solução, destacando o papel de cada componente e o fluxo de comunicação entre eles:

Figura 1 – Componentes da Arquitetura o Sistema

🌐 Visão geral

O sistema foi projetado para permitir acesso remoto seguro ao Hub ESP32 hospedado em um servidor Linux ou Raspberry Pi, utilizando o Cloudflare Quick Tunnel como ponte entre a rede local e a Internet.
Todo o processo ocorre automaticamente a cada inicialização do sistema, garantindo que o Hub permaneça acessível mesmo que o IP local ou público varie.

Componentes principais

🖥️ 1. Servidor Linux / Raspberry Pi (Hub Local)

É o núcleo da solução.
Nele residem:

  • O servidor web Nginx, que hospeda o dashboard do Hub e as interfaces HTML para comunicação com os ESP32;
  • O serviço cloudflared, responsável por criar o túnel reverso com o Cloudflare;
  • O serviço cf-url-notify, que monitora os logs do túnel e envia a URL pública atual ao ntfy.sh.

Esse servidor opera como ponto de concentração dos dados coletados pelos nós ESP32 (coletoras), além de disponibilizar a visualização e controle remoto.

☁️ 2. Cloudflare Quick Tunnel

O Quick Tunnel é a ponte entre o Hub local e o mundo externo.
Ao ser iniciado, o cloudflared estabelece uma conexão segura com os servidores da Cloudflare e publica um endereço público dinâmico no domínio trycloudflare.com, permitindo que o Hub seja acessado sem IP fixo ou configurações de roteador.

A cada reinicialização, o túnel gera uma nova URL, por exemplo:

https://josh-commerce-ann-humidity.trycloudflare.com

Limitações dos Quick Tunnels

É importante observar que os Quick Tunnels do Cloudflare possuem restrições que podem impactar determinadas arquiteturas de IoT ou aplicações com alta taxa de requisições. Cada túnel está limitado a 200 requisições simultâneas (in-flight), e ao atingir esse teto o Cloudflare retornará HTTP 429 (Too Many Requests) para novas conexões. Além disso, Quick Tunnels não suportam Server-Sent Events (SSE), o que inviabiliza o uso desse mecanismo para atualizações contínuas de dados. Projetos que dependam de streaming em tempo real ou alto volume de acessos devem considerar o uso de tunnels permanentes (com domínio próprio) ou outras soluções mais robustas.

Observação:

Ao iniciar os testes, avaliamos também a opção de utilizar um Named Tunnel do Cloudflare. No entanto, essa modalidade exige que o usuário tenha um domínio próprio registrado e delegado ao Cloudflare, além de ativar recursos do Zero Trust — etapas que, no Brasil, frequentemente solicitam um cartão de crédito mesmo no plano gratuito. Como o objetivo deste projeto é oferecer uma solução prática, acessível e replicável por qualquer leitor sem custos adicionais, optamos pelo Quick Tunnel. Essa opção não exige domínio, conta ou configurações adicionais, funcionando imediatamente e permitindo que a URL pública seja obtida automaticamente a cada inicialização. Com a automação criada neste guia, o Quick Tunnel se torna simples, funcional e perfeitamente adequado ao propósito de expor o HubESP32 pela Internet.

🔔 3. Serviço ntfy.sh

O ntfy.sh é um sistema de notificações via HTTP e MQTT que permite o envio instantâneo de mensagens para o celular ou navegador.
Neste projeto, ele é usado para notificar o administrador sobre a nova URL pública criada pelo túnel.
A mensagem enviada contém um link direto para o acesso remoto do Hub.

Exemplo de notificação recebida:

[Hub ESP32 - Cloudflare Tunnel]
Nova URL: https://josh-commerce-ann-humidity.trycloudflare.com

📡4. Usuário e Acesso Remoto

O administrador, ao receber a notificação, pode clicar diretamente no link informado e acessar a interface web do Hub, mesmo que o servidor esteja por trás de NAT ou firewall residencial.
O acesso pode ser feito a partir de qualquer dispositivo conectado à Internet — notebook, smartphone ou desktop.

🔌 5. Dispositivos ESP32 (Coletoras e Sensores)

As estações ESP32 comunicam-se com o Hub via rede local (Wi-Fi ou Ethernet).
Cada uma possui um proxy configurado no servidor (via Nginx) que permite redirecionar requisições e monitorar status individualmente.
Assim, o Hub centraliza dados de sensores, gráficos e comandos de controle — enquanto o túnel expõe apenas a interface do Hub, mantendo os dispositivos internos protegidos.

🔁 6. Fluxo de inicialização resumido

  1. O Linux/Raspberry Pi inicializa e inicia o serviço cloudflared;
  2. O túnel é criado e uma nova URL pública é gerada;
  3. O serviço cf-url-notify detecta a nova URL nos logs e testa sua acessibilidade;
  4. A URL é enviada ao tópico configurado no ntfy.sh;
  5. O usuário recebe a notificação e acessa remotamente o Hub via navegador.

🧩Acesso remoto ao servidor (via PuTTY e SSH/mDNS)

Para administrar o servidor Linux (ou o Raspberry Pi 4) onde o Hub será instalado, é prático usar o acesso remoto via SSH. Assim, todo o processo de configuração pode ser feito a partir do seu computador Windows, sem necessidade de teclado, mouse ou monitor conectados ao Raspberry.

🔹 Instalando e usando o PuTTY (Windows)

  1. Baixe o PuTTY no site oficial:
    👉 https://www.putty.org
  2. Instale e abra o aplicativo.
    Na tela principal, informe o host e a porta:

    • Host Name (or IP address):
      raspberrypi.local (ou o nome mDNS do seu Linux)
      → caso o mDNS não esteja ativo, use o IP da máquina.
    • Port: 22
    • Connection type: SSH
  3. Clique em Open.
    Na primeira conexão, o PuTTY exibirá um aviso de segurança — confirme com Yes.
  4. Faça login:
login as: <seu usuário>
password: ******

(informe o usuário configurado no seu Linux/Raspberry Pi)

Figura 2 – Tela Inicial do Putty

Figura 3 – Tela de Login do Putty

Figura 4 – Tela do Putty Logado

🔹 Ativando o SSH e o mDNS no Linux/Raspberry Pi

Habilitar o serviço SSH

sudo systemctl enable ssh
sudo systemctl start ssh

Após isso, você poderá acessar:

ssh usuario@meu-linux.local

ou, pelo PuTTY:

Host: meu-linux.local
Port: 22

💡 No Raspberry Pi OS, o SSH já vem instalado (basta habilitá-lo via raspi-config)
e o mDNS já está ativo por padrão.
Em outras distribuições (Debian, Ubuntu Server, etc.), pode ser necessário habilitar manualmente conforme acima.

🔹 Testando o mDNS e SSH

No terminal do seu computador (Windows PowerShell ou Linux):

ping raspberrypi.local
ssh usuario@raspberrypi.local

Se responder, o serviço está corretamente configurado.

🔹 Dica opcional – acesso remoto com chave SSH

Para evitar digitar senhas:

ssh-keygen -t ed25519
ssh-copy-id usuario@meu-linux.local

Assim, o login pelo PuTTY (ou VS Code SSH) será automático e mais seguro.

🔹 Resumo rápido

Função

Comando principal

Instalar mDNS

sudo apt install avahi-daemon -y

Habilitar SSH

sudo systemctl enable –now ssh

Testar mDNS

ping raspberrypi.local

Acessar via PuTTY

Host: raspberrypi.local, Port: 22, Type: SSH

Login

usuario / senha do Linux

 

Na configuração do Raspberry PI para o Putty enxergar as acentuações: Vá ao Menu Preferences | Localisation | Locale  e deve-se definir:

Language : pt (portuguese)
Country: BR (Brazil)
Character Set: UTF-8


Configuração do Ambiente de Rede e do Servidor Local

Nesta seção faremos a configuração do Hub para rodar em um Linux ou Raspberry PI fazendo o apontamento para duas aplicações rodando em ESP32’s diferentes na mesma rede local. As duas aplicações ficarão disponíveis para o acesso direto  através da Internet sem ter IP público, sem domínio próprio registrado, sem liberação de portas pelo provedor de Internet, sem DNS dinâmico, etc.

As duas aplicações já foram publicadas no Blog da Eletrogate. Sugerimos ao leitor dar uma olhada em cada projeto mencionado para melhor entendimento do tutorial aqui apresentado. São elas:

  1. Controle Centralizado: ESP-01 com Relé e DHT11 via ESP-NOW usando ESP32 – rodando no IP local 192.168.18.15
  2. ESP32 com Notificações via ntfy.sh – rodando no IP local 192.168.18.33

Importante: Foi necessário adaptar os javascripts dos html’s originais para não usar referências absolutas, mas sim referências relativas. Isso foi necessário, pois no Hub, os html’s estão sob Proxies e não diretamente em uma rede local.

Exemplo da mudança no primeiro html: tivemos que criar uma função pathBase() e, na rotina inicializaWebSocket(), comentamos as duas linhas iniciais e substituímos por outras três linhas.

function pathBase() {
  // se a URL tiver /d/algumaCoisa no começo, preserve; senão, vazio
  const m = location.pathname.match(/^\/d\/\d+(?=\/|$)/);
  return m ? m[0] : ''; // ex.: "/d/1" ou ""
}

function inicializaWebSocket() {
  //const proto = location.protocol === 'https:' ? 'wss' : 'ws'; 
  //ws = new WebSocket(`${proto}://${location.host}/ws`);
  const proto = location.protocol === 'https:' ? 'wss' : 'ws';
  const base  = pathBase();               // "/d/1" no túnel, "" na LAN
  ws = new WebSocket(`${proto}://${location.host}${base}/ws`);  
  ws.onmessage = (ev)=>{
    let msg;
    try { msg = JSON.parse(ev.data); } catch(e){ return; }

    if (msg.type === 'snapshot') {
      applySnapshot(msg.nodes);
    } else if (msg.type === 'update') {
      renderCard(msg);
    } else if (msg.type === 'remove') {
      removeCard(msg.mac);          
    }
  };
}

Com o ambiente básico preparado e a arquitetura compreendida, passamos agora à configuração prática do servidor local.
Nesta etapa, instalaremos e validaremos os componentes que formam o núcleo da comunicação: o servidor Nginx, o serviço Cloudflared, e os testes de conectividade local e remota.

🧩 1. Instalação e configuração do Nginx

O Nginx será o servidor web local responsável por hospedar o painel do Hub ESP32.
Ele também atuará como ponto de entrada para os proxies que redirecionam as requisições para cada ESP32 coletor da rede local.

Instale com:

sudo apt update
sudo apt install nginx -y

Verifique o status do serviço:

sudo systemctl status nginx

Se tudo estiver correto, o serviço deve aparecer como active (running).

🗂️ 2. Estrutura de diretórios do Hub

A seguir, criaremos uma estrutura simples e organizada para os arquivos do Hub:

sudo mkdir -p /var/www/hubesp32
sudo chown -R $USER:$USER /var/www/hubesp32

Crie um arquivo index.html inicial e mais simples como opção 1:

cat <<'EOF' > /var/www/hubesp32/index.html
<!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>Hub ESP32 - Teste</title>
</head>
<body>
<h2>Hub ESP32 ativo!</h2>
<p>Este é o painel principal hospedado no Nginx local.</p>
<!-- ESP1 -->
<a class="card" href="/d/1/">Rede ESP-NOW — ESP01</a>
<br><br>
<!-- ESP2 -->
<a class="card" href="/d/2/">Notificações via ntfy.sh — ESP02</a>
</body>
</html>
EOF

Ou crie um arquivo index.html inicial mais elaborado como opção 2:

cat <<'EOF' > /var/www/hubesp32/index.html
<!doctype html>
<html lang="pt-BR" data-theme="system">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
  <title>Hub ESP32</title>
  <style>
    /* ---------- Tema (variáveis) ---------- */
    :root{
      --bg:#0b0c10; --card:#11151b; --text:#e6e8eb; --muted:#9aa4b2; --accent:#00d2ff;
      --radius:18px; --shadow:0 8px 24px rgba(0,0,0,.25); --size:18px;
    }
    @media (prefers-color-scheme: light){
      :root{ --bg:#f7f7fb; --card:#ffffff; --text:#262933; --muted:#5d6677; --accent:#0077ff; --shadow:0 8px 24px rgba(0,0,0,.08); }
    }
    /* Força claro/escuro quando usuário escolhe */
    [data-theme="light"]{
      --bg:#f7f7fb; --card:#ffffff; --text:#262933; --muted:#5d6677; --accent:#0077ff; --shadow:0 8px 24px rgba(0,0,0,.08);
    }
    [data-theme="dark"]{
      --bg:#0b0c10; --card:#11151b; --text:#e6e8eb; --muted:#9aa4b2; --accent:#00d2ff; --shadow:0 8px 24px rgba(0,0,0,.25);
    }

    /* ---------- Layout ---------- */
    html,body{height:100%;margin:0;}
    body{
      font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Arial,sans-serif;
      background:var(--bg); color:var(--text); font-size:var(--size);
      display:flex; align-items:center; justify-content:center; padding:24px;
    }
    .wrap{ width:100%; max-width:720px; }
    .card{
      background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow);
      padding:20px 18px; position:relative;
    }
    .title{ display:flex; align-items:center; gap:12px; margin:6px 6px 14px; }
    .title .logo{ width:36px; height:36px; display:grid; place-items:center;
      border-radius:12px; background:linear-gradient(135deg,var(--accent),#7f5af0); color:white; font-weight:800; }
    h1{ font-size:1.4rem; line-height:1.2; margin:0; }
    p.sub{ margin:2px 0 0; color:var(--muted); font-size:.95rem; }
    .grid{ display:grid; gap:14px; grid-template-columns:1fr; margin:12px 6px 4px; }
    @media (min-width:560px){ .grid{ grid-template-columns:1fr 1fr; } }
    .app{
      border:1px solid rgba(127,127,127,.18); border-radius:16px; padding:14px 12px;
      display:flex; flex-direction:column; gap:10px;
      background:linear-gradient(180deg, color-mix(in oklab, var(--card), #000 2%), var(--card));
    }
    .app h2{ margin:0; font-size:1.05rem; }
    .badges{ display:flex; gap:8px; flex-wrap:wrap; color:var(--muted); font-size:.9rem;}
    .btn{
      appearance:none; -webkit-appearance:none; cursor:pointer; text-decoration:none;
      display:inline-flex; align-items:center; justify-content:center; gap:.6em;
      padding:12px 14px; border-radius:12px; font-weight:600; border:0;
      color:#fff; background:linear-gradient(135deg,var(--accent),#7f5af0);
      box-shadow:0 6px 16px rgba(0,0,0,.25);
    }
    .btn:active{ transform:translateY(1px); }
    .footer{ text-align:center; color:var(--muted); font-size:.9rem; margin-top:14px; }

    /* ---------- Alternador de tema ---------- */
    .theme-toggle{
      position:absolute; top:12px; right:12px;
      display:flex; align-items:center; gap:8px;
      background:transparent; border:1px dashed rgba(127,127,127,.35);
      color:var(--muted); border-radius:12px; padding:6px 10px; cursor:pointer;
      font-weight:600;
    }
    .theme-toggle:hover{ border-style:solid; }
    .theme-toggle .dot{ width:8px; height:8px; border-radius:999px; background:var(--accent); }
    /* Tamanho bom em telas pequenas */
    @media (max-width:400px){
      :root{ --size:17px; }
      .btn{ padding:14px 16px; }
    }
  </style>
</head>
<body>
  <div class="wrap">
    <div class="card">
      <button id="themeBtn" class="theme-toggle" aria-label="Alternar tema">
        <span class="dot"></span><span id="themeLabel">🖥️ Sistema</span>
      </button>

      <div class="title">
        <div class="logo">ESP</div>
        <div>
          <h1>Hub ESP32</h1>
          <p class="sub">Selecione uma aplicação</p>
        </div>
      </div>

      <div class="grid">
        <div class="app">
          <h2>Rede ESP-NOW de ESP01</h2>
          <div class="badges">
            <span>WebSocket ✔</span>
            <span>Painéis dinâmicos</span>
          </div>
          <a class="btn" href="/d/1/">Abrir aplicação</a>
        </div>

        <div class="app">
          <h2>Notificações via Ntfy.sh</h2>
          <div class="badges">
            <span>Formulário de envio</span>
            <span>Proxy /send</span>
          </div>
          <a class="btn" href="/d/2/">Abrir aplicação</a>
        </div>
      </div>

      <div class="footer">Protegido por autenticação • Cloudflare Quick Tunnel</div>
    </div>
  </div>

  <script>
    // -------- Alternador de tema --------
    const root = document.documentElement;
    const btn  = document.getElementById('themeBtn');
    const lab  = document.getElementById('themeLabel');

    // Estados possíveis: 'system' | 'light' | 'dark'
    function applyTheme(mode){
      root.setAttribute('data-theme', mode);
      localStorage.setItem('hubTheme', mode);
      lab.textContent = mode === 'light' ? '☀️ Claro' : mode === 'dark' ? '🌙 Escuro' : '🖥️ Sistema';
    }

    // Inicializa com preferência salva (ou 'system')
    applyTheme(localStorage.getItem('hubTheme') || 'system');

    // Alterna ciclicamente: system -> light -> dark -> system...
    btn.addEventListener('click', ()=>{
      const now = root.getAttribute('data-theme') || 'system';
      applyTheme(now === 'system' ? 'light' : now === 'light' ? 'dark' : 'system');
    });

    // Ajuste fino para telas com densidade alta
    if (window.devicePixelRatio && devicePixelRatio > 2) {
      document.documentElement.style.setProperty('--size','19px');
    }
  </script>
</body>
</html>
EOF

cat <<'EOF' > /var/www/hubesp32/index.html

<!doctype html>

<html lang="pt-br">

<head>

  <meta charset="utf-8">

  <title>Hub ESP32 - Teste</title>

</head>

<body>

  <h2>Hub ESP32 ativo!</h2>

  <p>Este é o painel principal hospedado no Nginx local.</p>

</body>

</html>

EOF

🌐 3. Configuração do servidor virtual (site do Hub)

Crie o arquivo de configuração do Nginx:

sudo nano /etc/nginx/sites-available/hubesp32

E adicione o conteúdo abaixo:

map $http_upgrade $connection_upgrade { default upgrade; "" close; }

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    auth_basic           "Hub ESP32";
    auth_basic_user_file /etc/nginx/.htpasswd;

    root /var/www/hubesp32;
    index index.html;

    # Garante que a raiz SEMPRE cai no nosso index.html
    location = / { try_files /index.html =404; }
    location /  { try_files $uri /index.html; }

    # Proxy ESP32 #1 (WebSocket/HTTP)
    location ^~ /d/1/ {
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        rewrite ^/d/1/(.*)$ /$1 break;
        proxy_pass http://192.168.18.15:80/;
    }

    # Proxy ESP32 #2 (ajuste o IP)
    location ^~ /d/2/ {
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        rewrite ^/d/2/(.*)$ /$1 break;
        proxy_pass http://192.168.18.33:80/;
    }

    # Rota exata /d/2/send -> envia POST direto ao ESP32-2
    location = /d/2/send {
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.18.33:80/send;
    }

    # Rota exata /send (quando o HTML usa action="/send")
    location = /send {
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.18.33:80/send;
    }

    location ~* \.(js|css|png|jpg|ico)$ {
        add_header Cache-Control "public, max-age=3600";
    }
}

Ative o site e reinicie o Nginx:

sudo ln -s /etc/nginx/sites-available/hubesp32 /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Teste localmente:

curl -I http://127.0.0.1/

Se tudo estiver correto, você verá algo como:

HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)

☁️ 4. Instalação e teste do Cloudflare Quick Tunnel

Agora instalaremos o Cloudflared, cliente oficial da Cloudflare. Uma atenção especial deve ser tomada para a questão da arquitetura do sistema para selecionar a versão adequada do Cloudflare.

Para o Linux com arquitetura amd64:

sudo mkdir -p /etc/cloudflared
cd /usr/local/bin
sudo wget -O cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
sudo chmod +x cloudflared

Para o Raspberry Pi na arquitetura arm64:

cd ~
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/cloudflared

Teste o funcionamento do túnel localmente:

cloudflared tunnel --url http://127.0.0.1:80

Após alguns segundos, deve aparecer uma URL pública no formato a seguir. Não dê CTRL-C e procure a URL subindo a tela do terminal.

https://valor-nomeado-aleatorio.trycloudflare.com

Acesse essa URL em outro dispositivo conectado à Internet.
Se a página “Hub ESP32 ativo!” aparecer, o túnel está funcionando corretamente.
Pressione Ctrl+C para encerrar o teste.

⚙️ 5. Configuração do serviço systemd para o Cloudflared

Crie o arquivo de serviço:

sudo nano /etc/systemd/system/cloudflared.service

Conteúdo:

[Unit]
Description=cloudflared (Quick Tunnel - 127.0.0.1:80)
After=network.target

[Service]
ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate --url http://127.0.0.1:80
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target

Ative e inicie o serviço:

sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared

Verifique o status:

sudo systemctl status cloudflared

E veja a URL pública gerada:

journalctl -u cloudflared -n 30 --no-pager | grep trycloudflare

🧩 6. Definição de Credenciais do Hub

 

Como o Hub vai ser acessado direto pela Internet é fundamental definir credenciais para acesso ao Hub. Estamos definindo a autenticação BASIC mas dentro do contexto https e do tunnel seguro da Cloudflare.

As credenciais do Hub (usuário/senha) são definidas no arquivo de autenticação usado pelo Nginx:

/etc/nginx/.htpasswd

Esse arquivo é criado com o comando (exemplo):

sudo htpasswd -c /etc/nginx/.htpasswd <nome_usuario>

Ele grava o usuário e uma hash da senha.

Se este comando htpasswd não estiver disponível, será necessário instalar o pacote a seguir:

sudo apt update
sudo apt install apache2-utils

Para adicionar outro usuário, sem apagar o existente:

sudo htpasswd /etc/nginx/.htpasswd <novo_usuario>

Para ver os usuários registrados:

cat /etc/nginx/.htpasswd

(⚠️ vai mostrar apenas as hashes — não há como recuperar as senhas originais)

Essas credenciais são referenciadas dentro da configuração do Nginx:

Para ver o arquivo:

cat /etc/nginx/sites-available/hubesp32

Observe as linhas a seguir no arquivo:

auth_basic "Hub ESP32";
auth_basic_user_file /etc/nginx/.htpasswd;

Ou seja:

  • Se quiser mudar a senha: htpasswd /etc/nginx/.htpasswd <nome_usuario>
  • Se quiser desativar o login temporariamente: basta comentar essas duas linhas no /etc/nginx/conf.d/hub.conf.

Com essa configuração, o Hub ESP32 já estará acessível via Internet por meio de uma URL pública temporária — e o túnel será recriado automaticamente em cada inicialização do sistema.

No próximo passo, implementaremos a automação completa com notificações via ntfy.sh, para que o administrador receba a nova URL pública a cada boot sem precisar consultar logs manualmente.


Automação com systemd e Notificações via ntfy.sh

O Quick Tunnel oferece acesso instantâneo à rede local, mas sua URL muda a cada inicialização.
Para evitar a necessidade de consultar manualmente os logs, criaremos um serviço auxiliar (cf-url-notify) que:

  1. Monitora a criação do túnel no journalctl;
  2. Extrai a nova URL pública gerada pelo Cloudflare;
  3. Aguarda a propagação da URL (aguardando resposta HTTP 200);
  4. Envia automaticamente uma notificação ao ntfy.sh, contendo o link direto de acesso ao Hub ESP32.

Passo 1 : Criar o serviço cloudflared.service

Isso vai deixar o Quick Tunnel subindo automaticamente no boot:

sudo tee /etc/systemd/system/cloudflared.service >/dev/null <<'EOF'
[Unit]
Description=cloudflared (Quick Tunnel - 127.0.0.1:80)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/cloudflared tunnel --url http://127.0.0.1:80
Restart=always
RestartSec=5
User=root

[Install]
WantedBy=multi-user.target
EOF

Recarregue o systemd e já habilite o serviço:

sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared.service
sudo systemctl status cloudflared.service --no-pager -n 20

Se o status mostrar active (running) e no log aparecer uma URL https://...trycloudflare.com, o túnel está ok.

 

Passo 2: Criando o script de monitoramento e notificação

Crie o arquivo:

sudo nano /usr/local/bin/cf-url-notify.sh

Conteúdo completo:

#!/bin/bash
# cf-url-notify.sh - Monitora logs do Cloudflared e envia a URL pública via ntfy.sh
# Autor: Dailton de Oliveira Menezes (projeto Hub ESP32 com Cloudflare Tunnel)

set -e

TOPIC="dom_07c2_07e9_alerts"    # Tópico de destino no ntfy.sh
SERVICE="cloudflared.service"
TMPFILE="/tmp/cf_url_current"
NTFY_URL="https://ntfy.sh/${TOPIC}"

echo "[cf-url-notify] Aguardando URL pública do serviço ${SERVICE} (boot atual)..."

# Aguarda até o Cloudflared gerar a URL
URL=""
for i in {1..60}; do
  URL=$(journalctl -u ${SERVICE} -b --no-pager | grep -o 'https://[a-zA-Z0-9.-]*\.trycloudflare\.com' | tail -n1)
  if [ -n "$URL" ]; then
    echo "[cf-url-notify] URL detectada: $URL"
    break
  fi
  sleep 5
done

if [ -z "$URL" ]; then
  echo "[cf-url-notify] Nenhuma URL detectada após o tempo limite."
  exit 1
fi

# Evita reenvio da mesma URL
if [ -f "$TMPFILE" ] && grep -q "$URL" "$TMPFILE"; then
  echo "[cf-url-notify] Mesma URL já enviada neste boot: $URL (nada a fazer)"
  exit 0
fi

# Testa a disponibilidade da URL
for i in {1..10}; do
  if curl -s --max-time 3 -I "$URL" | grep -q "200"; then
    echo "[cf-url-notify] URL acessível."
    break
  fi
  echo "[cf-url-notify] Aguardando URL ficar acessível..."
  sleep 10
done

# Envia notificação
MESSAGE="Nova URL pública do Hub ESP32 disponível:"
curl -s -X POST \
  -H "Title: 🌐 Hub ESP32 Online" \
  -H "Priority: high" \
  -H "Tags: globe_with_meridians,rocket" \
  -d "${MESSAGE} ${URL}" \
  "${NTFY_URL}"

echo "$URL" > "$TMPFILE"
echo "[cf-url-notify] Notificação enviada: $URL"

Permita execução:

sudo chmod +x /usr/local/bin/cf-url-notify.sh

Passo 3: Criando o serviço systemd cf-url-notify

Crie o arquivo de serviço:

sudo nano /etc/systemd/system/cf-url-notify.service

Conteúdo:

[Unit]
Description=Enviar URL do Quick Tunnel para ntfy.sh
After=cloudflared.service
Requires=cloudflared.service

[Service]
ExecStart=/usr/local/bin/cf-url-notify.sh
Type=oneshot
User=root
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

Passo 4: Ativando e testando o serviço

Ative e rode manualmente para testar:

sudo systemctl daemon-reload
sudo systemctl enable cf-url-notify.service
sudo systemctl start cf-url-notify.service

Verifique o log:

journalctl -u cf-url-notify.service -n 30 --no-pager

Você deverá ver algo como:

[cf-url-notify] Aguardando URL pública do serviço cloudflared (boot atual)...
[cf-url-notify] URL detectada: https://lucky-wavey-puma.trycloudflare.com
[cf-url-notify] URL acessível.
[cf-url-notify] Notificação enviada: https://lucky-wavey-puma.trycloudflare.com

E pouco depois, a notificação deve aparecer no aplicativo ou painel web do ntfy.sh.

Passo 5: Teste completo após reboot

Por fim, teste a automação completa:

sudo reboot

Após o sistema reiniciar:

  1. O serviço cloudflared criará automaticamente um novo túnel;
  2. O serviço cf-url-notify detectará a nova URL e enviará a notificação ao ntfy.sh;
  3. Você receberá no seu dispositivo a nova URL pública funcional do Hub ESP32.

Com essa automação, o ambiente se torna autônomo e resiliente, garantindo que o acesso remoto esteja sempre disponível e notificado — mesmo após quedas de energia, reboots ou mudanças de IP.

Validação e Testes do Sistema

Com todos os componentes devidamente configurados — Nginx, Cloudflared, e o serviço de notificação automática via ntfy.sh —, é hora de realizar uma sequência de testes para validar o comportamento do sistema e confirmar que ele está pronto para operação contínua.

🧠 Objetivo da validação

A validação garante que:

  1. O servidor web local (Hub ESP32) está acessível pela rede interna e externa;
  2. O Cloudflare Tunnel inicia automaticamente a cada boot e gera uma nova URL pública;
  3. O serviço cf-url-notify detecta essa nova URL e envia corretamente a notificação ao ntfy.sh;
  4. O sistema permanece funcional mesmo após reinicializações ou perda temporária de conexão com a Internet.

🧩 1. Verificação do servidor web local

Primeiro, verifique se o Nginx está respondendo normalmente na rede local:

curl http://127.0.0.1

Deve retornar o conteúdo da página inicial criada anteriormente:

<h2>Hub ESP32 ativo!</h2>

Também é possível acessar pelo navegador de outro dispositivo conectado à mesma rede local:

http://<IP_DO_LINUX_LOCAL>

Exemplo:

http://192.168.18.18

Se a página aparecer corretamente, o servidor Nginx está funcional.

☁️ 2. Verificação do túnel Cloudflare

Liste os logs recentes do serviço Cloudflared:

journalctl -u cloudflared -n 30 --no-pager

Procure por uma linha semelhante a:

INF | https://josh-commerce-ann-humidity.trycloudflare.com

Acesse essa URL em outro dispositivo conectado à Internet.
Se a página “Hub ESP32 ativo!” for exibida, o túnel está operando normalmente.

📬 3. Verificação da notificação automática (cf-url-notify)

Agora verifique se o serviço de notificação foi executado corretamente:

journalctl -u cf-url-notify.service -n 20 --no-pager

A saída esperada será algo como:

[cf-url-notify] URL detectada: https://josh-commerce-ann-humidity.trycloudflare.com
[cf-url-notify] URL acessível.
[cf-url-notify] Notificação enviada: https://josh-commerce-ann-humidity.trycloudflare.com

Logo após, uma mensagem deverá aparecer no aplicativo ntfy.sh, contendo o link direto para o Hub.

🔁 4. Teste de persistência após reboot

Para garantir o funcionamento contínuo, reinicie o sistema:

sudo reboot

Após o reinício:

  1. Aguarde cerca de 1 a 2 minutos;
  2. Verifique se uma nova notificação chegou com uma nova URL pública;
  3. Teste o acesso remoto clicando no link recebido.

A mudança da URL confirma que o Quick Tunnel foi recriado e o serviço cf-url-notify executou com sucesso.

⚙️ 5. Teste de reconexão de rede

Este teste simula uma interrupção de Internet:

  1. Desconecte temporariamente o cabo de rede (ou desligue o Wi-Fi);
  2. Aguarde cerca de 30 segundos;
  3. Reconecte a rede e observe os logs:
sudo journalctl -u cloudflared -n 20 --no-pager

O serviço Cloudflared deve reativar automaticamente a conexão com o túnel, sem necessidade de reiniciar o sistema.

🔍 6. Dicas de diagnóstico

Problema observado Possível causa Ação recomendada
❌ Sem resposta do túnel público Cloudflared não inicializou corretamente Verificar systemctl status cloudflared
🔕 Notificação não chegou ao ntfy.sh Falha de rede ou token incorreto Testar manualmente com curl e revisar tópico do ntfy
🌐 Página do Hub inacessível Erro no Nginx ou conflito de porta Verificar sudo nginx -t e logs em /var/log/nginx/error.log
🚫 URL antiga enviada Script cf-url-notify.sh não detectou novo boot Verificar data e conteúdo de /tmp/cf_url_current

 

🧾 7. Resultado esperado

Após esses testes, o comportamento esperado do sistema é:

  • O Hub ESP32 é acessível via rede local (http://192.168.x.x);
  • O Quick Tunnel é iniciado automaticamente em cada boot;
  • O ntfy.sh recebe e entrega uma notificação contendo a nova URL pública;
  • Acesso remoto seguro e contínuo sem necessidade de IP fixo ou redirecionamento de portas.

 


Adicionando novos ESP32 ao Hub

Para adicionar mais ESP32 ao Hub, basicamente, será necessário alterar os arquivos de configuração do Hub e o index.html.

Para ver o arquivo do Hub:

cat /etc/nginx/sites-available/hubesp32

Padrão de nomenclatura (recomendado)

  • Caminhos estáveis no Hub:

    • https://<URL_PUBLICA>/d/1/ → encaminha para http://192.168.18.15/

    • https://<URL_PUBLICA>/d/2/ → encaminha para http://192.168.18.33/

    • (adicione quantos precisar: /d/3/, /d/4/ …)

  • Mantemos o sufixo / no final para facilitar “caminhos relativos” das páginas do ESP.

Dica: fixe IPs dos ESP32 via DHCP reservation no seu roteador para não perder o mapeamento.

Configuração do Nginx (reverse proxy + WebSocket)

Edite o arquivo do site do Hub:

sudo nano /etc/nginx/sites-available/hubesp32

Dentro do bloco server { ... }, adicione um location por ESP

Exemplo para ESP3 (IP local 192.168.18.50), adicione o bloco a seguir ao arquivo :

 # Proxy ESP32 #3 (ajuste o IP)
location ^~ /d/3/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
rewrite ^/d/3/(.*)$ /$1 break;
proxy_pass http://192.168.18.50:80/;
}

Altere o index.html para adicionar ao html:

sudo nano /var/www/hubesp32/index.html

Adicione a referência ao novo ESP3, algo do tipo:

<!-- ESP3 --> 
<a class="card" href="/d/3/">Nova Aplicação — ESP03</a>

Teste e recarregue:

sudo nginx -t
sudo systemctl reload nginx

Neste ponto a referência ao novo ESP3 já estará disponível.

Suporte a outras rotas que o ESP possa ter

Como a aplicação o ESP está sob um PROXY, uma atenção especial deve ser tomada para outras rotas que por acaso a aplicação utiliza.

Se a aplicação do ESP faz, por exemplo, fetch('/send', {method: 'POST', ...}) e o ESP está sob o PROXY,  a requisição vai ser passada para o PROXY e não para o ESP dando o erro 404 ou 405 porque o servidor web do Hub só conhece a entrada de cada ESP. Portanto, precisamos criar uma regra adicional para cada requisição que o ESP envie informando ao PROXY para devolver a requisição para o ESP.

No nosso exemplo, a aplicação ESP2 envia a requisição /send para o envio da notificação e por isso criamos uma regra adicional para garantir que a requisição retorne do PROXY para o ESP. Veja como ficou o bloco:

 # Rota exata /send (quando o HTML usa action="/send")
location = /send {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.18.33:80/send;
} 

Boas práticas e pegadinhas

  • Caminhos relativos no ESP: com proxy_pass http://IP/; o ESP “acha” que está na raiz.
    Evite hardcode de caminhos absolutos; prefira relativos.

  • WebSocket no ESP: padronize caminho (/ws) e não force wss:// no código do ESP; deixe o Nginx/Cloudflare fazer TLS.

  • CORS: como o Hub e o proxy usam o mesmo host/porta, normalmente não precisa de CORS.
    Se for chamar APIs externas, aí sim ajuste CORS no Nginx ou no fetch.

  • Tempo de leitura: dashboards com SSE/WS podem precisar de proxy_read_timeout 3600s.

  • IPs fixos: reserve IP dos ESP32 no DHCP para que /espN/ nunca quebre.

Checklist para “mais um ESP”

  1. Fixe o IP do novo ESP (ex.: 192.168.18.53);

  2. Duplique um bloco location /d/x/ { ... } no /etc/nginx/sites-available/hubesp32 trocando IP e caminho;

  3. sudo nginx -t && sudo systemctl reload nginx;

  4. Adicione o card correspondente em /var/www/hubesp32/index.html;

  5. Teste curl -I http://127.0.0.1/d/x/ e, por fim, pela URL pública do Cloudflare.


Operação, logs e troubleshooting

Checagens rápidas (sanity checks)

A) Nginx local

# Teste de sintaxe
sudo nginx -t

# Está ativo?
systemctl is-active nginx && systemctl is-enabled nginx

# Responde na 80?
curl -I --max-time 3 http://127.0.0.1/

B) Cloudflared (Quick Tunnel)

# Status do serviço
sudo systemctl status cloudflared --no-pager -n 30

# Últimas linhas de log (boot atual)
journalctl -b -u cloudflared -n 80 --no-pager

# Ver URL pública atual (a partir do log)
journalctl -b -u cloudflared --no-pager \
  | grep -Eo 'https://[a-z0-9-]+\.trycloudflare\.com' \
  | tail -1

C) Proxies para ESPs

# Deve retornar HTTP 200/401/301 (dependendo do seu setup)
curl -I --max-time 3 http://127.0.0.1/d/1/
curl -I --max-time 3 http://127.0.0.1/d/2/

Se o Hub tem Basic Auth, use -u usuario:senha no curl.

Logs úteis (onde olhar e o que procurar)

A) Nginx

# Acessos
sudo tail -n 200 /var/log/nginx/access.log

# Erros (proxy, upstream, WebSocket, etc.)
sudo tail -n 200 /var/log/nginx/error.log

# Filtrar um ESP específico
sudo grep '/esp1/' -n /var/log/nginx/access.log | tail -n 50

B) Cloudflared

journalctl -b -u cloudflared -n 200 --no-pager

C) Automação da URL (timer opcional, se você usa)

systemctl list-timers | grep cf-url-notify
journalctl -u cf-url-notify.service -n 80 --no-pager

Browser (DevTools)

  • Network → WS: confirme que o upgrade do WebSocket virou 101 Switching Protocols.

  • Frames: verifique se chegam mensagens snapshot/update.

  • Console: erros de CORS, Mixed Content, TypeError: ws is undefined, etc.

Erros comuns e correções

Sintoma / Log

Causa provável

Correção

405 Not Allowed vindo do Hub

Método não roteado no proxy encurtador (ex.: POST /esp2/send)

Crie location = /esp2/send { proxy_pass http://IP/send; } ou deixe o ESP aceitar o caminho “sem encurtador” via location /esp2/

502/504 no Nginx

ESP desligado/fora do ar; IP mudou; timeout curto

Verifique IP (reserva DHCP), aumente proxy_read_timeout e proxy_send_timeout

WS não conecta (fica “pending”)

Upgrade/Connection não propagados

Garanta no server do Nginx: proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade, proxy_set_header Connection $connection_upgrade

WS fecha após alguns segundos

Timeout do proxy

Aumente proxy_read_timeout 3600s;

Mixed Content no navegador

Página HTTPS chamando ws:// explícito

No front use const proto = location.protocol===’https:’?’wss’:’ws’

401 Unauthorized no Hub

Basic Auth ativo

No teste use curl -u user:senha … (ou desative na fase de debug)

URL pública “antiga” no push

Captura antes do Cloudflared “subir”

Espere alguns segundos e leia a última ocorrência via `journalctl …

 

Sequência segura de restart

# 1) Nginx
sudo nginx -t && sudo systemctl restart nginx

# 2) Cloudflared
sudo systemctl restart cloudflared
sleep 5
journalctl -b -u cloudflared --no-pager \
  | grep -Eo 'https://[a-z0-9-]+\.trycloudflare\.com' | tail -1

# 3) ESP(s) — se necessário
# (reinicie o dispositivo físico ou via OTA)

Testes de ponta a ponta

A) Pela URL pública

PUB=$(journalctl -b -u cloudflared --no-pager \
  | grep -Eo 'https://[a-z0-9-]+\.trycloudflare\.com' | tail -1)

# Hub (página inicial)
curl -I --max-time 6 "$PUB/"

# ESP1 via Hub
curl -I --max-time 6 "$PUB/esp1/"

# Envio POST (se tiver encurtador)
curl -i --max-time 6 -X POST "$PUB/esp2/send" -d 't=1'

B) WebSocket pelo navegador

Abra https://URL_PUBLICA/d/1/, pressione F12 > Network > WS, confirme 101 e frames chegando.

Hardening rápido (opcional, mas recomendado)

Basic Auth já configurado? Trocar/sincronizar credenciais:

# (se estiver usando htpasswd)
sudo htpasswd /etc/nginx/.htpasswd seu_usuario
sudo systemctl reload nginx

Checklist “deu ruim, e agora?”

  •  O Hub abre em http://127.0.0.1/?

  •  A URL pública aparece no journalctl -b -u cloudflared?

  •  O card do ESP abre localmente (curl -I http://127.0.0.1/esp1/)?

  •  O mesmo card abre pela URL pública?

  •  Para WS: status 101 e frames?

  •  POSTs retornam 2xx?

  •  Logs do Nginx/Cloudflared mostram erro? Qual?

  •  IPs dos ESPs estão fixos e respondendo na LAN?

  • As IA’s podem ajudar muito na solução de problemas. Neste projeto usei muito o ChatGPT.

Telas do Hub

Figura 5 – Tela do Hub no Desktop

Figura 6 – Tela do Hub no Celular

Figura 7 – Tela Simples do Hub no Celular

Figura 8 – Tela App1 do Hub no Desktop

Figura 9 – Tela App1 do Hub no Celular

Figura 10 – Tela App2 do Hub no Desktop

Figura 11 – Tela App2 do Hub no Celular

 


Telas do Serviço de Notificação ntfy.sh

Figura 12 – Tela Web do Serviço de Notificação Ntfy.sh

Figura 13- Tela do App do Serviço de Notificação Ntfy.sh


Referências

💻 Sistemas Operacionais

  • Ubuntu Linux (Download oficial)
    👉 https://ubuntu.com/download
    Página oficial da Canonical com versões Desktop e Server. Recomendado para máquinas x86.
  • Raspberry Pi OS (Instalação e Imager Tool)
    👉 https://www.raspberrypi.com/software/
    Contém o Raspberry Pi Imager, usado para gravar o sistema no cartão microSD e preparar o Pi para o primeiro boot.

🔐 Acesso Remoto e SSH

🌐 Serviços e Comunicação

⚙️ Servidor e Componentes

  • Nginx (Servidor Web leve e eficiente)
    👉 https://nginx.org/en/docs/
    Página oficial do projeto Nginx com documentação e exemplos de configuração.
  • OpenSSL (Geração de chaves e certificados SSH)
    👉 https://www.openssl.org/docs/
    Ferramenta usada para geração de chaves e suporte a conexões seguras, útil em projetos avançados.

💡 Leituras Complementares


Conclusão

Este projeto buscou demonstrar que é possível construir uma infraestrutura segura e totalmente automatizada para hospedar dashboards e serviços locais de IoT sem depender de IPs válidos na Internet, ou domínio registrado, ou roteamento avançado ou conta cadastrada na Cloudflare com fornecimento de cartão de crédito. A combinação entre o Quick Tunnel da Cloudflare, o servidor Nginx e o sistema de notificações ntfy.sh fornece uma base extremamente flexível, especialmente para ambientes dinâmicos com endereços públicos variáveis.

Embora o Quick Tunnel não ofereça persistência de endereço como o Named Tunnel, o uso de um serviço no Linux para monitorar a criação da URL e notificar automaticamente o administrador compensa essa limitação com eficácia. O resultado é um ambiente totalmente funcional, gratuito, que pode ser inicializado, atualizado e acessado remotamente com mínimo esforço.

Além disso, a estrutura modular adotada — com o hub local servindo como ponto central de acesso aos módulos ESP32 — facilita futuras expansões, como a integração de novos dispositivos, sensores ou painéis de controle. Essa arquitetura distribui a complexidade, mas mantém o controle dentro da rede local.

Buscamos também demonstrar que os mesmos princípios poderão ser aplicados, sem alterações significativas, para o Raspberry Pi 4. Isso demonstra a portabilidade e a escalabilidade da solução, tornando-a ideal para laboratórios, sistemas de automação residencial e projetos didáticos.

Por fim, o projeto mostra como tecnologias de código aberto podem ser combinadas para entregar resultados profissionais. Espera-se que este guia sirva de base para novas ideias e inspire a criação de soluções ainda mais inteligentes e acessíveis.

Com poucos recursos e um pouco de curiosidade, é possível transformar um simples servidor local em uma janela segura para o acesso de qualquer lugar do mundo.


Sobre o Autor


Alberto de Almeida Menezes
tinho.menezes@gmail.com

Bacharel em Engenharia de Áudio e Produção Musical pela Berklee College of Music.


Dailton de Oliveira Menezes
dailton.menezes@gmail.com

Bacharel em Ciência da Computação pela Universidade Federal de Minas Gerais.


Eletrogate

21 de novembro de 2025

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Os comentários estão desativados.

Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!

Eletrogate Robô

Assine nossa newsletter e
receba  10% OFF  na sua
primeira compra!