Murad Library
RESEARCH#md

RESEARCH

Tutorial: Tornando um Forgejo privado via Tailscale

tutorial_forgejo_privado_tailscale.md

research·#MD·tutorial_forgejo_privado_tailscale.md
Date
Reading
7 min read

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 hosts em 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:

  1. confirmar que o Forgejo era o causador da carga;
  2. fechar a porta SSH pública do Forgejo;
  3. restringir o vhost Nginx para aceitar apenas IPs da Tailscale;
  4. configurar DNS interno com dnsmasq;
  5. distribuir esse DNS pela Tailscale;
  6. 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

Tutorial: Tornando um Forgejo privado via Tailscale · Murad Library