RESEARCH
Tutorial: Tornando um Forgejo privado via Tailscale
Este tutorial documenta o processo de diagnóstico e correção de consumo alto de CPU causado por acessos ao Forgejo/Git, além da configuração para deixar o Forgejo acessível apenas pela rede Tailscale.
Dados sensíveis foram ocultados.
IPs reais, domínio real, nomes de host, usuários, caminhos internos específicos e demais identificadores foram substituídos por placeholders.
1. Objetivo
Deixar uma instância Forgejo funcionando como um Git privado:
- acessível por todos os dispositivos autorizados na Tailnet da Tailscale;
- bloqueada para a internet pública;
- com HTTPS funcionando no domínio escolhido;
- com clone/push via HTTPS e SSH funcionando apenas pela Tailnet;
- sem precisar editar o arquivo
hostsem cada máquina.
2. Sintomas iniciais
O servidor apresentava carga alta, identificada com:
uptime
Também foram vistos vários processos relacionados a Forgejo/Gitea e Git consumindo CPU:
/usr/local/bin/gitea web
/usr/bin/git -c protocol.version=2 ...
Isso apontava para operações Git pesadas, como clones, fetches, crawlers ou acessos automatizados.
3. Confirmando que o Forgejo era o causador
Para confirmar o diagnóstico, o Forgejo foi parado temporariamente:
cd /CAMINHO/DO/FORGEJO
docker compose down
Depois, a carga foi acompanhada:
uptime
Como a carga começou a cair, ficou claro que o Forgejo era o principal causador do consumo.
4. Verificando os containers e portas
Após subir novamente:
cd /CAMINHO/DO/FORGEJO
docker compose up -d
Foi verificado o estado dos containers:
docker compose ps
Exemplo sanitizado de saída:
NAME SERVICE STATUS PORTS
forgejo server Up 0.0.0.0:222->22/tcp, 127.0.0.1:PORTA_WEB->3000/tcp
forgejo-db db Healthy 3306/tcp
Interpretação:
127.0.0.1:PORTA_WEB->3000/tcp: a interface web estava presa ao localhost, correto.0.0.0.0:222->22/tcp: o SSH Git estava exposto para a internet pública, incorreto.
5. Fechando o SSH do Forgejo para a internet pública
No docker-compose.yml, a publicação da porta SSH estava semelhante a:
ports:
- "127.0.0.1:PORTA_WEB:3000"
- "222:22"
A linha SSH foi alterada para escutar apenas no IP Tailscale do servidor:
ports:
- "127.0.0.1:PORTA_WEB:3000"
- "IP_TAILSCALE_DO_SERVIDOR:222:22"
Exemplo com placeholders:
ports:
- "127.0.0.1:32784:3000"
- "100.X.Y.Z:222:22"
Depois, os containers foram recriados:
cd /CAMINHO/DO/FORGEJO
docker compose down
docker compose up -d
Validação:
docker compose ps
ss -tulpn | grep ':222'
Resultado correto:
IP_TAILSCALE_DO_SERVIDOR:222->22/tcp
Resultado incorreto:
0.0.0.0:222->22/tcp
6. Descobrindo o arquivo Nginx realmente ativo
O primeiro arquivo editado não era o vhost ativo. Para descobrir o arquivo correto:
nginx -T 2>/dev/null | grep -nA90 -B10 "server_name DOMINIO_DO_FORGEJO"
Também foi usado:
grep -R "root /home/USUARIO_SITE/htdocs/DOMINIO_DO_FORGEJO" /etc/nginx/sites-enabled /etc/nginx/conf.d 2>/dev/null
O arquivo ativo tinha o formato:
/etc/nginx/sites-enabled/DOMINIO_DO_FORGEJO.conf
7. Bloqueando acesso público no Nginx
Backup do vhost:
FILE="/etc/nginx/sites-enabled/DOMINIO_DO_FORGEJO.conf"
cp "$FILE" "$FILE.bak.$(date +%F-%H%M)"
Foi inserido o bloqueio global no bloco server HTTPS:
allow 100.64.0.0/10;
deny all;
A faixa 100.64.0.0/10 é a faixa CGNAT usada pelos IPs da Tailscale.
Comando usado para inserir automaticamente após server_name:
FILE="/etc/nginx/sites-enabled/DOMINIO_DO_FORGEJO.conf"
grep -q "BLOQUEIO GLOBAL: somente Tailscale" "$FILE" || sed -i '/server_name DOMINIO_DO_FORGEJO;/a\
\
# BLOQUEIO GLOBAL: somente Tailscale\
allow 100.64.0.0/10;\
deny all;\
' "$FILE"
Depois:
nginx -t && systemctl restart nginx
Teste público:
curl -I https://DOMINIO_DO_FORGEJO
Resultado esperado fora da Tailnet:
HTTP/2 403
8. Validando acesso via Tailscale
Antes de mexer com DNS interno, foi feito um teste forçando resolução para o IP da Tailscale:
curl -k -I --resolve DOMINIO_DO_FORGEJO:443:IP_TAILSCALE_DO_SERVIDOR https://DOMINIO_DO_FORGEJO
Resultado esperado:
HTTP/2 200
Conclusão:
- pelo IP público: bloqueado com
403; - pelo IP Tailscale: liberado com
200.
9. Mantendo o reverse proxy apontando para localhost
A URL interna do reverse proxy deve apontar para a porta local publicada pelo Docker:
http://127.0.0.1:PORTA_WEB
Exemplo:
http://127.0.0.1:32784
Não é necessário apontar o reverse proxy para o IP Tailscale. O Nginx roda no próprio servidor e deve falar com o Forgejo localmente.
10. Por que não usar DNS público apontando para IP Tailscale
O painel DNS público pode rejeitar IPs da faixa 100.64.0.0/10, porque ela é uma faixa reservada/CGNAT.
Além disso, mesmo que algum provedor aceite, apontar o DNS público para IP Tailscale pode quebrar renovação HTTP-01 do Let's Encrypt.
A solução adotada foi usar Split DNS pela Tailscale.
11. Configurando DNS interno com dnsmasq
Instalação:
apt update
apt install -y dnsmasq dnsutils
Verificação da porta DNS:
ss -tulpn | grep ':53'
Foi criado o arquivo:
cat > /etc/dnsmasq.d/tailscale-forgejo.conf <<'EOF_DNSMASQ'
interface=tailscale0
listen-address=IP_TAILSCALE_DO_SERVIDOR
bind-interfaces
address=/DOMINIO_DO_FORGEJO/IP_TAILSCALE_DO_SERVIDOR
server=1.1.1.1
server=8.8.8.8
EOF_DNSMASQ
Exemplo sanitizado:
cat > /etc/dnsmasq.d/tailscale-forgejo.conf <<'EOF_DNSMASQ'
interface=tailscale0
listen-address=100.X.Y.Z
bind-interfaces
address=/git.exemplo.com/100.X.Y.Z
server=1.1.1.1
server=8.8.8.8
EOF_DNSMASQ
Depois:
systemctl restart dnsmasq
systemctl enable dnsmasq
Teste local:
dig @IP_TAILSCALE_DO_SERVIDOR DOMINIO_DO_FORGEJO +short
Resultado esperado:
IP_TAILSCALE_DO_SERVIDOR
Verificação de escuta:
ss -tulpn | grep 'IP_TAILSCALE_DO_SERVIDOR:53'
Resultado esperado:
udp ... IP_TAILSCALE_DO_SERVIDOR:53 ... dnsmasq
tcp ... IP_TAILSCALE_DO_SERVIDOR:53 ... dnsmasq
12. Distribuindo DNS pela Tailscale
No painel administrativo da Tailscale:
Admin Console -> DNS -> Nameservers -> Add nameserver -> Custom
Adicionar:
IP_TAILSCALE_DO_SERVIDOR
Usar Split DNS restrito ao domínio do Forgejo:
DOMINIO_DO_FORGEJO
Se o domínio exato não funcionar bem, usar o domínio-base:
DOMINIO_BASE
Exemplos sanitizados:
git.exemplo.com
ou:
exemplo.com
13. Testando em um PC da Tailnet
No Windows PowerShell, testar consulta direta ao DNS interno:
nslookup DOMINIO_DO_FORGEJO IP_TAILSCALE_DO_SERVIDOR
Resultado esperado:
Name: DOMINIO_DO_FORGEJO
Address: IP_TAILSCALE_DO_SERVIDOR
Depois testar a resolução padrão:
nslookup DOMINIO_DO_FORGEJO
Se ainda não resolver corretamente:
tailscale down
tailscale up
ipconfig /flushdns
Clear-DnsClientCache
Restart-Service Tailscale
Teste HTTP:
curl.exe -I https://DOMINIO_DO_FORGEJO
Resultado esperado dentro da Tailnet:
HTTP/2 200
14. Clonando repositórios do Forgejo
Via HTTPS
De uma máquina conectada à Tailnet:
git clone https://DOMINIO_DO_FORGEJO/USUARIO/REPOSITORIO.git
Via SSH
Como a porta SSH está presa ao IP da Tailscale:
git clone ssh://git@DOMINIO_DO_FORGEJO:222/USUARIO/REPOSITORIO.git
Teste de autenticação SSH:
ssh -T -p 222 git@DOMINIO_DO_FORGEJO
Fora da Tailnet, o acesso não deve funcionar.
15. Importando repositórios do GitHub para o Forgejo
O bloqueio foi feito apenas para entrada no Forgejo. O servidor continua podendo sair para a internet:
Forgejo -> GitHub
Portanto, é possível importar repositórios GitHub pelo painel do Forgejo em:
New Migration / Migrar repositório
Para repositórios públicos:
https://github.com/USUARIO/REPOSITORIO.git
Para repositórios privados, usar um Personal Access Token do GitHub.
Também é possível migrar manualmente:
cd /tmp
git clone --mirror https://github.com/USUARIO/REPOSITORIO.git
cd REPOSITORIO.git
git push --mirror ssh://git@DOMINIO_DO_FORGEJO:222/USUARIO/REPOSITORIO.git
16. Verificando consumo após a correção
Comandos úteis:
uptime
docker stats --no-stream
ps aux --sort=-%cpu | head -25
O esperado é que o Forgejo não continue consumindo CPU de forma anormal após o bloqueio correto do Nginx e da porta SSH.
17. Estado final desejado
Docker
Forgejo web:
127.0.0.1:PORTA_WEB -> 3000/tcp
Forgejo SSH:
IP_TAILSCALE_DO_SERVIDOR:222 -> 22/tcp
Não deve existir:
0.0.0.0:222 -> 22/tcp
0.0.0.0:3000 -> 3000/tcp
Nginx
O vhost do Forgejo deve conter:
allow 100.64.0.0/10;
deny all;
DNS interno
Dentro da Tailnet:
DOMINIO_DO_FORGEJO -> IP_TAILSCALE_DO_SERVIDOR
Acesso
Dentro da Tailnet:
https://DOMINIO_DO_FORGEJO -> 200 OK
Fora da Tailnet:
https://DOMINIO_DO_FORGEJO -> 403 Forbidden
18. Conclusão
A causa provável do consumo alto era acesso externo ao Forgejo/Git, gerando operações Git caras.
A correção consistiu em:
- confirmar que o Forgejo era o causador da carga;
- fechar a porta SSH pública do Forgejo;
- restringir o vhost Nginx para aceitar apenas IPs da Tailscale;
- configurar DNS interno com
dnsmasq; - distribuir esse DNS pela Tailscale;
- validar acesso interno e bloqueio externo.
O resultado final é um Forgejo privado, acessível por todos os dispositivos autorizados na Tailnet e bloqueado para a internet pública.
Related documents
- 001
- 002
- 003
- 004
research · MD
100 jogos cozy para quem ama Stardew Valley - 005