No post anterior, mostrei como o SSH-J.com resolve um problema específico: acessar via SSH uma máquina que está atrás de NAT, sem abrir portas no roteador e sem depender de IP público. O túnel reverso funciona bem para sessões interativas e transferência de arquivos, e o SSH-J.com como jump host torna tudo trivial de configurar. Para SSH, continua sendo a solução mais simples que conheço.

Mas SSH é só uma peça do quebra-cabeça. Quem mantém um homelab — mesmo que seja só um mini PC embaixo da mesa ou um Raspberry Pi no canto da sala — inevitavelmente acaba rodando serviços web: um leitor de RSS, um dashboard de monitoramento, um Gitea, um Jellyfin, um Immich. Esses serviços escutam em portas HTTP locais e funcionam perfeitamente enquanto você está na mesma rede. O problema aparece quando você quer acessá-los de fora — do escritório, do celular no ônibus, de qualquer lugar que não seja a sua rede local.

As opções tradicionais são as mesmas de sempre: port forwarding no roteador (que esbarra no CGNAT e expõe portas para a internet), DDNS para lidar com IP dinâmico (que resolve apenas metade do problema), ou uma VPN como WireGuard ou Tailscale (que funciona mas exige um cliente instalado em cada dispositivo). O Cloudflare Tunnel oferece uma alternativa que não requer nenhuma dessas coisas: o serviço local faz uma conexão de saída para a rede da Cloudflare, que por sua vez publica o serviço em um subdomínio do seu domínio, com HTTPS configurado automaticamente. Nenhuma porta aberta, nenhum IP público, nenhum certificado para gerenciar. De fora, o acesso é um URL normal no browser.

Neste post, mostro como configurei o meu próprio setup: um leitor de RSS rodando em um container LXC no Proxmox, publicado em rss.sarmento.org através de um Cloudflare Tunnel gerenciado localmente via CLI e mantido por systemd.

Como o Cloudflare Tunnel funciona#

O princípio é o mesmo do túnel reverso do SSH-J.com, só que aplicado a tráfego HTTP em vez de sessões SSH. Um daemon chamado cloudflared roda na sua máquina local e abre conexões de saída para a rede de edge da Cloudflare. Como a conexão parte de dentro da sua rede, o NAT não é problema — o roteador trata como qualquer outra conexão de saída. A Cloudflare recebe as requisições HTTP destinadas ao seu subdomínio e as encaminha pelo túnel até o cloudflared, que por sua vez as repassa para o serviço local.

A diferença em relação ao SSH-J.com é que aqui não existe relay de terceiro operando com infraestrutura mínima — é a rede da Cloudflare, com mais de 300 pontos de presença no mundo, servindo de proxy reverso para o seu serviço. O HTTPS é provido pela Cloudflare usando certificados gerados automaticamente para o seu domínio. O tráfego entre o browser do visitante e a Cloudflare é criptografado normalmente com TLS. O tráfego entre a Cloudflare e o cloudflared na sua máquina viaja pela conexão de túnel, que por sua vez usa QUIC ou HTTP/2 com TLS. E entre o cloudflared e o serviço local, como ambos estão na mesma máquina, a comunicação acontece sobre localhost sem criptografia — o que é perfeitamente aceitável quando tudo roda no mesmo host.

Existem duas formas de gerenciar um Cloudflare Tunnel: pelo dashboard web (Zero Trust → Tunnels) ou pela CLI. O dashboard é mais visual e permite configurar tudo pelo browser, mas o resultado é um túnel “remotamente gerenciado” — a configuração fica na Cloudflare e o cloudflared local só precisa de um token para conectar. A CLI cria um túnel “localmente gerenciado” onde a configuração fica em um arquivo YAML na máquina, o que dá mais controle e visibilidade sobre o que está rodando. Neste post uso a CLI porque é o caminho que faz mais sentido para quem já está confortável com terminal e quer entender o que cada peça faz.

Pré-requisitos#

Antes de começar, você precisa de três coisas.

Um domínio próprio com o DNS gerenciado pela Cloudflare. Se o seu domínio já está na Cloudflare (por exemplo, porque você usa Cloudflare Pages para hospedar um site estático), esse requisito já está atendido. Se não está, o processo é adicionar o domínio na Cloudflare e apontar os nameservers do seu registrador para os que ela te fornece. A documentação oficial cobre isso em detalhes.

Uma conta gratuita na Cloudflare. O Cloudflare Tunnel faz parte do plano gratuito — não é necessário plano pago para uso pessoal.

Uma máquina Linux rodando o serviço que você quer expor. No meu caso é um container LXC com Debian 13 no Proxmox, mas pode ser qualquer distribuição com systemd. O serviço precisa estar escutando em uma porta local — no meu exemplo, um web app em http://127.0.0.1:8080.

Instalando o cloudflared#

O cloudflared é o daemon que estabelece e mantém o túnel. No Debian, a instalação pode ser feita pelo repositório oficial da Cloudflare ou baixando o .deb diretamente. Optei por baixar o pacote:

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

Confirme que a instalação funcionou:

cloudflared --version

A saída deve mostrar a versão instalada. Se você estiver em ARM (Raspberry Pi, por exemplo), troque amd64 por arm64 no URL do download.

Autenticando na Cloudflare#

O próximo passo é vincular o cloudflared à sua conta Cloudflare. Execute:

cloudflared tunnel login

O comando vai gerar um URL e pedir que você o abra no browser. Na página que abrir, faça login na sua conta Cloudflare e selecione o domínio que será usado para o túnel. Após a autorização, o cloudflared salva um certificado em ~/.cloudflared/cert.pem que será usado para criar e gerenciar túneis.

Se a máquina onde você está instalando o cloudflared não tem browser (que é o caso comum em servidores), copie o URL exibido no terminal e abra-o em qualquer outro computador onde você esteja logado na Cloudflare. O fluxo de autorização acontece no browser, não na máquina local.

Criando o túnel#

Com a autenticação feita, crie o túnel:

cloudflared tunnel create homelab

O nome homelab é um identificador que você escolhe — pode ser qualquer coisa descritiva. O comando cria o túnel na sua conta Cloudflare e gera um arquivo de credenciais em ~/.cloudflared/<UUID>.json, onde <UUID> é o identificador único do túnel. Anote esse UUID — você vai precisar dele na configuração.

Para confirmar que o túnel foi criado:

cloudflared tunnel list

A saída mostra o nome, o UUID e o status de cada túnel associado à sua conta.

Configurando o roteamento#

O túnel existe, mas ainda não sabe para onde encaminhar o tráfego. Essa configuração é feita em um arquivo YAML. Crie ~/.cloudflared/config.yml:

tunnel: <UUID>
credentials-file: /root/.cloudflared/<UUID>.json

ingress:
  - hostname: rss.sarmento.org
    service: http://127.0.0.1:8080
  - service: http_status:404

Troque <UUID> pelo UUID real do seu túnel e ajuste o hostname e a porta para o seu caso. O bloco ingress define as regras de roteamento: requisições para rss.sarmento.org são encaminhadas para o serviço local na porta 8080, e qualquer outra requisição recebe um 404. A última regra com service: http_status:404 é obrigatória — o cloudflared exige uma regra catch-all no final do ingress.

Se você quiser expor mais de um serviço pelo mesmo túnel, basta adicionar entradas ao ingress antes da regra catch-all:

ingress:
  - hostname: rss.sarmento.org
    service: http://127.0.0.1:8080
  - hostname: git.sarmento.org
    service: http://127.0.0.1:3000
  - service: http_status:404

Cada hostname precisa de um registro DNS na Cloudflare, que é o próximo passo.

Criando o registro DNS#

O cloudflared tem um comando que cria automaticamente o registro CNAME apontando o subdomínio para o túnel:

cloudflared tunnel route dns homelab rss.sarmento.org

Esse comando cria um registro CNAME no DNS da Cloudflare apontando rss.sarmento.org para <UUID>.cfargotunnel.com. A partir desse momento, qualquer requisição para rss.sarmento.org vai bater na rede da Cloudflare, que vai procurar um túnel ativo com aquele UUID para encaminhá-la.

Você pode verificar o registro no dashboard da Cloudflare, em DNS → Records do seu domínio. O registro CNAME deve aparecer lá com o status de proxy ativo (nuvem laranja).

Testando manualmente#

Antes de criar o serviço systemd, teste o túnel manualmente para confirmar que tudo funciona:

cloudflared tunnel run homelab

O cloudflared deve conectar à rede da Cloudflare e começar a mostrar logs no terminal. Abra https://rss.sarmento.org no browser — o serviço local deve aparecer, servido com HTTPS, sem nenhuma configuração adicional de certificado. O terminal vai mostrar as requisições sendo encaminhadas. Quando estiver satisfeito que tudo funciona, interrompa com Ctrl+C.

Rodando como serviço com systemd#

O cloudflared tem um comando integrado que cria e configura o serviço systemd automaticamente:

sudo cloudflared service install

Esse comando faz três coisas: copia a configuração de ~/.cloudflared/config.yml para /etc/cloudflared/config.yml, copia o arquivo de credenciais para /etc/cloudflared/, e cria a unit file em /etc/systemd/system/cloudflared.service com o conteúdo adequado. A unit file gerada se parece com isto:

[Unit]
Description=cloudflared
After=network.target

[Service]
TimeoutStartSec=0
Type=notify
ExecStart=/usr/bin/cloudflared --no-autoupdate --config /etc/cloudflared/config.yml tunnel run
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

O Type=notify indica que o cloudflared avisa o systemd quando está pronto para receber tráfego — uma integração mais sofisticada do que o Restart=always que usamos no post do SSH-J.com. O --no-autoupdate desabilita o mecanismo de atualização automática do cloudflared, que é o comportamento correto quando você gerencia pacotes pelo sistema operacional.

Ative e inicie o serviço:

sudo systemctl enable --now cloudflared

Confirme que está rodando:

sudo systemctl status cloudflared

A saída deve mostrar active (running) e os logs de conexão. A partir de agora o túnel inicia automaticamente no boot e reconecta em caso de falha.

Diferenças em relação ao SSH-J.com#

Vale colocar as duas soluções lado a lado para entender quando usar cada uma.

O SSH-J.com é ideal para acesso SSH a máquinas atrás de NAT. Não precisa de conta, não precisa de domínio, e a configuração inteira é um comando SSH. A contrapartida é que o tráfego passa por um servidor de terceiros (embora criptografado ponta a ponta) e o serviço se limita a encaminhar conexões TCP — não serve para expor aplicações web com HTTPS.

O Cloudflare Tunnel serve para expor serviços HTTP e HTTPS na internet com um hostname próprio. O HTTPS vem de graça, o DNS é gerenciado pela Cloudflare, e o tráfego passa pela rede de edge deles. Em compensação, exige um domínio com DNS na Cloudflare, instalação do cloudflared na máquina, e uma configuração um pouco mais envolvida. É possível usar o Cloudflare Tunnel para SSH também (a Cloudflare tem documentação específica para isso), mas a complexidade é consideravelmente maior do que simplesmente usar o SSH-J.com.

Na prática, as duas soluções coexistem sem conflito. Uso o SSH-J.com para acessar máquinas via SSH quando estou fora de casa, e o Cloudflare Tunnel para publicar web apps do homelab com domínio e HTTPS. Cada ferramenta resolve o problema para o qual foi desenhada.

Quando algo dá errado#

O túnel conecta mas o site não carrega#

O problema mais comum é um descompasso entre o hostname configurado no ingress e o registro DNS criado na Cloudflare. Se o CNAME aponta para um UUID diferente do que está no config.yml, o tráfego não chega ao túnel certo. Verifique com:

cloudflared tunnel info homelab

Compare o UUID exibido com o que está no config.yml e no registro CNAME do dashboard.

Outro cenário frequente é o serviço local não estar rodando ou estar escutando em um endereço ou porta diferente do configurado. Se o ingress aponta para http://127.0.0.1:8080 mas o serviço está na porta 3000, o cloudflared vai receber um connection refused e retornar um erro 502 para o browser. Teste o serviço localmente antes de culpar o túnel:

curl http://127.0.0.1:8080

Se o curl funciona mas o browser não, o problema está entre o cloudflared e a Cloudflare, não entre o cloudflared e o serviço local.

O serviço systemd fica reiniciando#

Se o systemctl status cloudflared mostra ciclos de startfailedauto-restart, o problema quase certamente está na configuração. Olhe os logs:

journalctl -u cloudflared -n 50 --no-pager

Os erros mais comuns são: arquivo de credenciais não encontrado (o cloudflared service install não copiou corretamente, ou o path no config.yml está errado), YAML inválido no config.yml (indentação errada, campo faltando), ou falta da regra catch-all no final do ingress.

Certificado SSL inválido no browser#

Se o browser mostra erro de certificado ao acessar o subdomínio, verifique no dashboard da Cloudflare se o modo SSL/TLS está configurado como “Full” ou “Flexible” — não “Off”. A Cloudflare gera o certificado automaticamente para o seu domínio, mas o modo SSL precisa estar ativo para que ele seja servido. Isso normalmente já é o padrão para domínios com proxy ativo.