SSH atrás de NAT? SSH-J.com resolve.

Neste post
Você trabalha remoto, tem um servidor em casa, um Raspberry Pi rodando serviços, ou uma máquina no escritório que precisa acessar de vez em quando. O cenário é comum e a solução óbvia é o SSH — que já está instalado, é seguro e funciona há décadas. O problema é que entre a sua máquina e o resto da internet existe um roteador, um NAT, e possivelmente um provedor que não te dá IP público fixo ou que bloqueia portas de entrada. De repente, o protocolo mais confiável da administração de sistemas se torna inacessível de fora da sua rede local.
As soluções tradicionais existem e funcionam: abrir portas no roteador com port forwarding, configurar DDNS para lidar com IP dinâmico, montar uma VPN com WireGuard ou Tailscale, ou alugar um VPS barato para servir de bastion host. Cada uma tem seu lugar, mas todas adicionam infraestrutura, configuração e em alguns casos custo recorrente — tudo isso para resolver o que deveria ser um problema simples: conectar via SSH a uma máquina que está ligada e funcionando, mas que o mundo exterior não consegue alcançar.
O SSH-J.com oferece uma abordagem diferente: um jump host público e gratuito que funciona como ponte entre você e a sua máquina atrás de NAT. Sem conta, sem cadastro, sem instalação de software adicional. A configuração inteira se resume a dois comandos SSH e um serviço systemd para manter tudo rodando automaticamente. Neste post, mostro como configurar do zero, partindo de uma máquina Debian que acabou de ser instalada até um acesso remoto funcional e persistente.
O problema: SSH atrás de NAT#
O NAT — Network Address Translation — é o mecanismo que permite que dezenas de dispositivos em uma rede local compartilhem um único endereço IP público. O roteador mantém uma tabela de tradução que associa conexões internas a portas no IP externo, e tudo funciona de forma transparente para conexões de saída: quando a sua máquina abre uma conexão SSH para um servidor na internet, o roteador sabe para onde encaminhar as respostas porque ele mesmo criou a entrada na tabela.
O problema aparece na direção contrária. Uma conexão vinda de fora — alguém tentando fazer SSH na sua máquina — chega ao IP público do roteador, que não tem como saber para qual dispositivo interno encaminhá-la. Não existe entrada na tabela de NAT porque ninguém de dentro iniciou essa conexão. O pacote é descartado e, do ponto de vista de quem está tentando conectar, a máquina simplesmente não existe.
Port forwarding resolve isso de forma direta: você configura o roteador para encaminhar conexões na porta 22 (ou outra) para o IP interno da máquina. Mas nem sempre isso é viável. Em redes corporativas ou de coworking você não tem acesso ao roteador. Muitos provedores residenciais no Brasil usam CGNAT — Carrier-Grade NAT — que adiciona uma camada extra de NAT no lado do provedor, tornando o port forwarding no seu roteador inútil porque o IP “público” que você vê já é um IP privado compartilhado com outros assinantes. E mesmo quando o port forwarding funciona, expor a porta SSH diretamente para a internet exige atenção redobrada com hardening, fail2ban e monitoramento de tentativas de brute force.
A ideia por trás do SSH-J.com é inverter a direção da conexão. Em vez de esperar que alguém de fora consiga alcançar a sua máquina, a própria máquina abre uma conexão de saída para o SSH-J.com e publica sua porta SSH através de um túnel reverso. Como a conexão parte de dentro da rede local, o NAT não é obstáculo — o roteador trata como qualquer outra conexão de saída e mantém o caminho aberto. Quando você quer acessar a máquina, conecta ao SSH-J.com como jump host, e ele encaminha o tráfego pelo túnel que já está estabelecido.
SSH-J.com: um jump host público e gratuito#
O SSH-J.com é um serviço mantido por ValdikSS, desenvolvedor russo conhecido por projetos de contorno de censura e ferramentas de rede. O servidor roda uma versão modificada do Dropbear — um servidor SSH leve, comum em sistemas embarcados e roteadores — configurada exclusivamente para permitir túneis reversos e conexões via jump host. Não é um serviço comercial, não tem plano pago, não exige cadastro e não armazena dados de usuário. A infraestrutura é mínima por design: o servidor existe para encaminhar conexões, nada mais.

Como funciona#
O mecanismo usa dois recursos padrão do protocolo SSH que existem em qualquer cliente OpenSSH moderno: túnel reverso (-R) e jump host (-J).
Na primeira etapa, a máquina atrás de NAT abre uma conexão SSH para o SSH-J.com e cria um túnel reverso. O comando é uma única linha:
ssh meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22
O -N diz ao SSH para não abrir um shell remoto — a conexão existe apenas para manter o túnel. O -R cria o túnel reverso propriamente dito: ele instrui o SSH-J.com a escutar conexões destinadas a minha-maquina na porta 22 e encaminhá-las de volta para localhost:22 na máquina que originou a conexão. O nome minha-maquina é um identificador arbitrário que você escolhe — pode ser o hostname, um apelido, qualquer coisa que faça sentido para você.
Na segunda etapa, quando você quer acessar a máquina de qualquer lugar do mundo, usa o SSH-J.com como jump host:
ssh -J meuusuario@ssh-j.com minha-maquina
O -J faz o SSH conectar primeiro ao SSH-J.com e de lá estabelecer uma conexão TCP para minha-maquina:22, que o servidor resolve internamente pelo túnel reverso que está ativo. A autenticação acontece em duas camadas: a conexão com o SSH-J.com (que aceita qualquer usuário sem senha) e a conexão com a máquina de destino (que usa as credenciais reais configuradas nela — senha ou chave SSH, como em qualquer acesso SSH normal).
O username no SSH-J.com funciona como namespace. Os hosts publicados por um usuário ficam vinculados a ele — outros usernames não conseguem acessá-los. Isso significa que se alguém usar o mesmo nome de máquina que você mas com um username diferente, não há conflito. O limite é de 50 serviços publicados por username, mais do que suficiente para uso pessoal.
Segurança: o que o SSH-J vê (e o que não vê)#
A pergunta natural ao rotear tráfego SSH por um servidor de terceiros é: o que esse servidor pode ver? A resposta curta é que ele vê os metadados da conexão, mas não o conteúdo.
O SSH-J.com funciona como um relay de TCP. Ele sabe que uma conexão foi estabelecida entre o seu cliente e a sua máquina, conhece os IPs de origem e destino, e vê o volume de tráfego. Mas o conteúdo da sessão SSH é criptografado ponta a ponta entre o seu cliente e a máquina de destino — a negociação de chaves e a autenticação acontecem diretamente entre os dois endpoints, e o jump host não tem acesso ao material criptográfico necessário para decifrar o tráfego. Isso é uma propriedade do modo como o -J opera no OpenSSH: ele usa o jump host apenas como transporte TCP, não como proxy SSH que termina e reinicia a conexão.
Dito isso, você está confiando que o servidor é o que diz ser. O código-fonte do fork do Dropbear usado no SSH-J.com está disponível publicamente no Bitbucket do autor, mas você não tem como verificar que aquele binário específico é o que está rodando no servidor. Na prática, o nível de confiança necessário é semelhante ao de usar qualquer VPN ou proxy — e consideravelmente menor do que o exigido por soluções que instalam software proprietário na sua máquina. Para acesso a um servidor pessoal, um Raspberry Pi em casa ou uma máquina de desenvolvimento, o risco é aceitável. Para acesso a infraestrutura de produção com dados sensíveis, uma VPN dedicada ou um bastion host próprio continua sendo a escolha apropriada.
Configuração passo a passo#
Toda a configuração a seguir pode ser feita como usuário comum — não é necessário ser root para criar o túnel nem para conectar. A única etapa que exige privilégios de administrador é a criação do serviço systemd, que fica na próxima seção.
Publicando sua máquina com um túnel reverso#
Antes de tudo, a máquina que você quer acessar precisa ter o servidor SSH instalado e rodando. No Debian, se ele ainda não estiver ativo:
sudo apt install openssh-server
Com o SSH local funcionando, o primeiro passo é aceitar o host key do SSH-J.com. Isso precisa ser feito uma vez, interativamente, antes de qualquer automação:
ssh meuusuario@ssh-j.com
O SSH vai mostrar a fingerprint do servidor e perguntar se você aceita a conexão. Digite yes. A conexão vai se encerrar imediatamente porque o SSH-J.com não fornece shell interativo — é o comportamento esperado. O que importa é que a entrada foi gravada no ~/.ssh/known_hosts e conexões futuras não vão travar esperando confirmação.
Agora crie o túnel reverso:
ssh meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22
O comando não produz nenhuma saída — o cursor fica parado e o terminal fica ocupado. Isso é o esperado: a conexão está aberta e o túnel está ativo. Enquanto esse terminal estiver aberto, sua máquina está acessível via SSH-J.com.
Conectando de qualquer lugar#
De qualquer outro computador com acesso à internet, basta:
ssh -J meuusuario@ssh-j.com usuario@minha-maquina
O meuusuario é o namespace que você escolheu no SSH-J.com — precisa ser o mesmo usado no túnel reverso. O usuario é o login real na máquina de destino, que pode ser diferente. Se o nome de usuário local for o mesmo da máquina remota, a parte usuario@ pode ser omitida:
ssh -J meuusuario@ssh-j.com minha-maquina
Na primeira conexão, o SSH vai pedir para aceitar o host key da máquina de destino — não do SSH-J.com, que já foi aceito antes. Isso confirma que a criptografia é ponta a ponta: o seu cliente está verificando a identidade da máquina final, não do intermediário.
Ferramentas que usam SSH como transporte também funcionam normalmente pelo jump host. Para copiar um arquivo com scp:
scp -J meuusuario@ssh-j.com arquivo.txt usuario@minha-maquina:/tmp/
Para sincronizar um diretório com rsync:
rsync -avP -e "ssh -J meuusuario@ssh-j.com" ./pasta/ usuario@minha-maquina:/home/usuario/pasta/
Configurando o ~/.ssh/config no cliente#
Digitar o -J meuusuario@ssh-j.com toda vez funciona, mas cansa. A solução é adicionar a configuração no ~/.ssh/config da máquina de onde você conecta — o seu laptop, o seu Mac, o computador do escritório:
Host minha-maquina
HostName minha-maquina
User usuario
ProxyJump meuusuario@ssh-j.com
Com essa entrada, o acesso se reduz a:
ssh minha-maquina
E o scp e rsync também herdam a configuração automaticamente, sem precisar do -J:
scp arquivo.txt minha-maquina:/tmp/
rsync -avP ./pasta/ minha-maquina:/home/usuario/pasta/
Se você tem mais de uma máquina publicada no SSH-J.com, basta adicionar um bloco Host para cada uma. O namespace no SSH-J.com é o mesmo — o que diferencia é o nome do host no -R de cada túnel.
Automatizando com systemd#
O túnel reverso funciona enquanto a conexão SSH estiver aberta. Se a máquina reiniciar, se a rede cair por alguns segundos ou se o processo SSH morrer por qualquer motivo, o túnel desaparece e a máquina volta a ser inacessível. Para um teste rápido isso é aceitável, mas para acesso remoto confiável o túnel precisa se manter sozinho — levantar no boot e reconectar automaticamente em caso de falha.
O systemd resolve isso com um serviço simples (se quiser entender o ecossistema de units e dependências do systemd mais a fundo, escrevi um post sobre systemd timers). A partir daqui, os comandos precisam ser executados como root.
O serviço#
Crie o arquivo /etc/systemd/system/ssh-tunnel.service:
[Unit]
Description=SSH reverse tunnel via SSH-J.com
After=network.target
[Service]
User=meuusuariolocal
ExecStart=/usr/bin/ssh meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22
Restart=always
RestartSec=15
[Install]
WantedBy=multi-user.target
Alguns pontos sobre essa unit file. O User= define qual usuário do sistema vai executar o processo SSH — e consequentemente qual ~/.ssh/known_hosts e quais chaves SSH serão usadas. Esse precisa ser o mesmo usuário com o qual você aceitou o host key do SSH-J.com na seção anterior. O Restart=always combinado com RestartSec=15 faz o systemd reiniciar o processo sempre que ele morrer, esperando 15 segundos entre tentativas para não sobrecarregar o servidor com reconexões em rajada. O After=network.target garante que o serviço só tenta iniciar depois que a rede estiver configurada — sem isso, o SSH tentaria conectar antes de ter uma rota para a internet e falharia silenciosamente.
Aceitando o host key antes de ativar#
Esse passo é o que pega quem tenta automatizar o túnel sem ter testado manualmente antes. O SSH precisa que o host key do SSH-J.com esteja no known_hosts do usuário que vai rodar o serviço. Se não estiver, o SSH tenta perguntar interativamente se você aceita a chave — mas como o systemd não tem terminal, a pergunta nunca aparece, o SSH falha com Host key verification failed, e o systemd fica reiniciando o processo indefinidamente a cada 15 segundos sem nenhum resultado visível.
Se você seguiu a seção anterior e aceitou o host key como o usuário que está no User= do serviço, esse passo já está resolvido. Se não tem certeza, ou se o serviço vai rodar como um usuário diferente do que você usou antes, execute:
su - meuusuariolocal -c "ssh meuusuario@ssh-j.com"
Aceite a fingerprint com yes. A conexão vai fechar imediatamente — novamente, é o esperado. O que importou aconteceu: o host key foi gravado no ~/.ssh/known_hosts daquele usuário.
Habilitando e testando#
Com o host key aceito, ative o serviço:
systemctl daemon-reload
systemctl enable --now ssh-tunnel
O enable faz o serviço iniciar automaticamente no próximo boot. O --now faz ele iniciar também neste momento, sem precisar de um comando separado. Verifique se está rodando:
systemctl status ssh-tunnel
A saída deve mostrar active (running) e o processo SSH na árvore de processos. Se aparecer activating (auto-restart) alternando com failed, o problema quase certamente é o host key — volte ao passo anterior.
Para confirmar que o túnel está de fato funcional, vá até outra máquina e tente conectar:
ssh -J meuusuario@ssh-j.com minha-maquina
Se o acesso funcionar, o serviço está pronto. A partir de agora a máquina vai manter o túnel ativo permanentemente, reconectando sozinha após reinicializações ou quedas de rede. Você pode verificar o comportamento reiniciando a máquina e tentando conectar novamente depois que ela voltar — o túnel deve se restabelecer automaticamente dentro de alguns segundos após o boot.
Quando algo dá errado#
O túnel conecta mas o acesso falha#
O erro mais comum ao tentar conectar via jump host é:
channel 0: open failed: administratively prohibited:
stdio forwarding failed
Essa mensagem significa que o SSH-J.com recebeu a sua conexão mas não encontrou nenhum túnel reverso ativo com o nome de host que você pediu. Na prática, a máquina de destino não está publicada — ou porque o serviço do túnel não está rodando, ou porque o nome do host no -R não bate com o nome que você está usando no -J.
O diagnóstico começa pelo lado do servidor. Conecte na máquina (por outro meio, se necessário) e verifique o serviço:
systemctl status ssh-tunnel
Se estiver em failed ou em loop de auto-restart, o próximo passo é olhar o que o SSH está dizendo. Como o systemd pode não ter journal configurado em containers ou instalações mínimas, rode o comando manualmente com verbose para ver a saída real:
su - meuusuariolocal -c "ssh -v meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22"
A saída com -v mostra cada etapa da negociação SSH. Os problemas mais frequentes aparecem nessas linhas:
Host key verification failed — o known_hosts do usuário não tem a entrada do SSH-J.com. Aceite o host key interativamente como descrito na seção anterior.
remote forward failure for: listen — o nome de host que você está tentando publicar já está em uso por outra conexão, possivelmente uma sessão anterior que ainda não expirou. Espere alguns minutos para o SSH-J.com liberar o nome, ou escolha um nome diferente no -R.
Connection refused ao tentar conectar na máquina de destino — o túnel está ativo mas o servidor SSH da máquina não está rodando. Verifique com systemctl status ssh (no Debian, o serviço se chama ssh, não sshd).
Outro ponto que gera confusão é o username. O nome de usuário usado no SSH-J.com é um namespace — ele vincula os hosts publicados ao seu “espaço”. Se você criou o túnel com joao@ssh-j.com mas tenta conectar com maria@ssh-j.com, o jump host não vai encontrar a máquina porque ela foi publicada em outro namespace. O username no SSH-J.com precisa ser idêntico nos dois lados: no -R da máquina que publica e no -J do cliente que conecta.
Reconexão após queda de rede#
O Restart=always do systemd cuida de reiniciar o processo SSH quando ele morre, mas existem situações em que a conexão TCP fica em um estado intermediário — a rede caiu e voltou, mas o processo SSH ainda não percebeu que a conexão foi perdida. O SSH continua achando que o túnel está ativo enquanto o SSH-J.com já descartou a sessão. O resultado é um serviço que aparece como active (running) no systemd mas que na prática não está funcionando.
O SSH tem mecanismos nativos para detectar conexões mortas. Adicione estas opções ao comando no ExecStart do serviço:
ExecStart=/usr/bin/ssh meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22 \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes
O ServerAliveInterval=30 faz o SSH enviar um pacote de keep-alive a cada 30 segundos. Se o servidor não responder a três pacotes consecutivos (ServerAliveCountMax=3), o SSH encerra a conexão. Isso permite que o systemd detecte a falha e reinicie o processo em no máximo 90 segundos mais o RestartSec configurado.
O ExitOnForwardFailure=yes faz o SSH encerrar imediatamente se o túnel reverso não puder ser estabelecido — por exemplo, se o nome de host ainda estiver preso de uma sessão anterior. Sem essa opção, o SSH mantém a conexão aberta mesmo que o -R tenha falhado, e você fica com um serviço rodando que não está fazendo nada.
Com essas três opções, a unit file completa fica:
[Unit]
Description=SSH reverse tunnel via SSH-J.com
After=network.target
[Service]
User=meuusuariolocal
ExecStart=/usr/bin/ssh meuusuario@ssh-j.com -N -R minha-maquina:22:localhost:22 \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes
Restart=always
RestartSec=15
[Install]
WantedBy=multi-user.target
Após editar, recarregue e reinicie:
systemctl daemon-reload
systemctl restart ssh-tunnel
Essa combinação de keep-alive do SSH com restart automático do systemd cobre a grande maioria dos cenários de falha de rede. Para quem quer uma camada adicional de robustez, o autossh é uma alternativa que monitora a conexão de forma mais agressiva e reconecta proativamente, mas na prática as opções nativas do OpenSSH combinadas com o systemd já oferecem confiabilidade suficiente para uso pessoal.
Se o que você precisa expor não é SSH mas sim serviços web — um painel, um leitor de RSS, qualquer coisa que escuta em uma porta HTTP — escrevi um post sobre Cloudflare Tunnel que resolve esse cenário com o mesmo princípio de conexão de saída, mas com HTTPS automático e domínio próprio. E se o que você quer é conectar todos os seus dispositivos numa rede mesh privada, o Tailscale cobre SSH, serviços web e tudo mais sem expor nada à internet.
Sysadmin, 53 anos, brasileiro trabalhando de casa para o mundo todo. Cuida de servidores Linux, containers LXC, e de gatos que não saem de cima do teclado.