
O Gitea pulou da 1.27 para a 28 — e não, não foram 27 versões perdidas. A 28.0.0, anunciada em 30 de setembro de 2026, é a versão que se chamaria 1.28.0: o projeto simplesmente abandonou o “1.” que nunca saía do lugar. Por trás do número novo há uma release grande, com log de auditoria, contas de bot, deploy tokens, impersonação pelo administrador e várias melhorias nas Actions — e mudanças incompatíveis que exigem revisar segurança, retenção e workflows antes de atualizar. Este post resume o que mudou e conta o que aprendemos atualizando uma instância de produção.
Por que 28 e não 1.28
Desde o fork do Gogs, em 2016, o Gitea numerava as versões como 1.x. O primeiro número nunca mudou; quem carregava a informação era o segundo. A partir desta release, esse segundo número passou para a frente: 1.27 → 28. Não há reescrita nem quebra geral de compatibilidade por causa do número — há mudanças incompatíveis que precisam ser avaliadas, descritas mais abaixo.
O efeito prático é em automação: scripts que procuram tags 1.*, comparam versões como texto ou montam a URL de download na mão. Junto com a mudança, a 28 deixou de publicar binários x86 de 32 bits e as variantes gogit, e os nomes de arquivo perderam o sufixo de versão do sistema operacional. Para Linux amd64, o binário continua em https://dl.gitea.com/gitea/28.0.0/gitea-28.0.0-linux-amd64, com .sha256, assinatura GPG e pacote Sigstore ao lado.
Log de auditoria
Um recurso importante para quem administra Gitea em empresa. Eventos relevantes de segurança passam a ser registrados no estilo do GitHub: ação, autor, escopo, origem (interface, API, CLI ou sistema) e metadados. Os eventos aparecem nas configurações do administrador, da organização, do repositório e do usuário, com filtros. Ações feitas durante uma impersonação registram as duas pessoas.
O detalhe que importa: a gravação vem desligada. Para ligar, no app.ini:
[audit]
RECORD_OUTPUT = database
RETENTION_DAYS = 90 ; padrão 30; 0 guarda para sempre
A limpeza dos eventos antigos é feita pela tarefa cron.delete_old_audit_events.
Contas de bot e deploy tokens
Automação no Gitea costumava significar criar um usuário comum, gerar um token e torcer para ninguém logar com ele. A 28 traz contas de bot de verdade: usuários sem senha, que só autenticam por token, não recebem notificações nem e-mails e não conseguem abrir sessão interativa — nem por login, nem por proxy reverso, nem por fonte externa. Elas podem ser administradas pela interface, pela API ou pela CLI. O comando gitea admin user change-type converte uma conta local existente em bot ou faz a conversão inversa.
Os deploy tokens são o par das deploy keys para HTTPS: uma credencial limitada a um repositório, com leitura ou leitura e escrita, usada como senha numa operação Git — inclusive LFS. Servem para o servidor de produção puxar um repositório sem chave SSH e sem a conta de uma pessoa.
Completam o pacote os tokens pessoais regeneráveis: dá para trocar o valor de um token mantendo nome e permissões, útil quando ele vazou ou foi entregue a terceiros — o próprio PR cita o caso de um token passado a um agente de IA.
Administração e revisão de código
- Impersonação: o administrador pode ver a instância como um usuário específico para investigar um problema de permissão. A ação fica registrada quando o log de auditoria está habilitado.
- Code owners obrigatórios: uma nova regra de proteção de branch exige a aprovação de um proprietário ou integrante do time por regra correspondente no
CODEOWNERS. - Diff mais navegável: busca e filtro por extensão na barra lateral de arquivos, e linhas longas truncadas, mas visíveis.
- Notificações por WebSocket: o canal de eventos em tempo real (contador de notificações, cronômetro, logout) trocou SSE por WebSocket em
/-/ws. Se o WebSocket não conectar, a interface cai para consulta periódica.
Gitea Actions
As Actions — o CI/CD embutido, com YAML compatível com o da GitHub Actions — ganharam:
- Fila de builds: uma visão somente leitura dos jobs, com os em execução primeiro e depois os que estão aguardando, na ordem em que um runner vai pegá-los. Aparece na administração para a instância inteira e em cada repositório.
- Matriz dinâmica: o
strategy.matrixde um job pode ser montado a partir das saídas de jobs anteriores. max-parallelna matriz, cancelamento forçado de execuções pela API e mais endpoints para gerenciar execuções e logs.- Prévia de artefatos direto na página da execução.
No lado do executor, o runner chegou à 4.1.0 em 1º de outubro, com um backend para rodar os jobs no Kubernetes.
Retenção e egress: dois cuidados antes do upgrade
Comece pela retenção dos dados e pelas permissões de saída. Há outros requisitos de compatibilidade na seção seguinte.
1. O histórico de Actions passa a expirar
Até a 1.27, execuções concluídas ficavam no banco para sempre. A 28 cria o RUN_RETENTION_DAYS, com padrão de 400 dias: na limpeza da meia-noite seguinte ao upgrade, execuções mais antigas que isso são apagadas, junto com jobs, logs e artefatos. Sem um backup, não há restauração automática. Para manter o comportamento antigo, defina antes de atualizar:
[actions]
RUN_RETENTION_DAYS = 0 ; 0 = guardar para sempre
Atenção ao 0: agora ele significa “guardar para sempre” também em LOG_RETENTION_DAYS e ARTIFACT_RETENTION_DAYS. Até a 1.27, quem pusesse 0 nessas duas chaves tinha logs e artefatos apagados na limpeza seguinte.
2. O tráfego Git de saída passa por um proxy interno
Migrações, espelhos e outras operações de rede do Git agora passam por um proxy interno, que aplica as regras de egress às conexões diretas. As regras ganharam um modo:
EGRESS_MODE = lax(padrão): permite hosts públicos em qualquer porta, salvo bloqueios explícitos; endereços privados e de loopback exigem permissão.EGRESS_MODE = strict: só libera o que estiver na lista de permitidos, respeitando os bloqueios; entradas sem porta valem apenas para 80 e 443.

O alerta das notas de versão é específico: em [security], uma ALLOWED_HOST_LIST que antes restringia os destinos públicos deixa de ser exclusiva no modo padrão lax. Para manter essa restrição em webhooks e OAuth2, configure EGRESS_MODE = strict. Não copie essa configuração sem listar os destinos realmente necessários.
Há uma exceção importante na migração de configuração: em [migrations], se a lista nova estiver ausente, uma ALLOWED_DOMAINS legada mantém o modo estrito por compatibilidade e permite todas as portas dos hosts listados. Ao adotar a lista nova, revise as portas explicitamente. Consulte a referência de migrações, em vez de presumir equivalência entre chaves antigas e novas.
As listas controlam conexões diretas. Se houver um proxy de saída configurado, o bloqueio dos destinos encaminhados passa a ser responsabilidade desse proxy.
Além disso:
- O preset
externaldeixou de existir. - Curingas em endereços IP e a entrada
*não são mais aceitos. - Domínios seguem a sintaxe do curl:
example.comvale para o domínio e todos os subdomínios;*.example.com, só para os subdomínios. - Uma entrada inválida em
[migrations] BLOCKED_HOST_LISTimpede o Gitea de subir. ALLOWED_DOMAINS,BLOCKED_DOMAINSeALLOW_LOCALNETWORKSficam obsoletas em favor deALLOWED_HOST_LISTeBLOCKED_HOST_LIST.- O
[migrations]controla migrações e espelhos; o[security], webhooks e OAuth2.
Outras mudanças incompatíveis
- Git mínimo: a versão 28 exige Git 2.25.0 ou posterior. Confira
git --versionno ambiente que executa o Gitea. - Cadastro: o autorregistro fica desativado por padrão. Para permiti-lo deliberadamente, configure
[service] DISABLE_REGISTRATION = false. - URL da instância:
[server] DOMAINdeixa de ser lido; reviseROOT_URL, usado também para derivar o domínio SSH padrão. - Workflows: o
if:do job é avaliado antes da expansão da matriz. Condições commatrixprecisam ser movidas para etapas ou para a configuração da matriz. - Falha na matriz:
strategy.fail-fastpassa a ser aplicado; configurefalsequando todas as combinações precisarem terminar. - Workflows reutilizáveis: repositórios públicos não podem chamar workflows privados, e chamadas aninhadas não podem elevar as permissões do token do chamador.
Esses pontos fazem parte das mudanças incompatíveis documentadas pelo projeto.
Na prática: atualizando uma instância de produção
Na atualização de uma instância instalada como binário com systemd e MySQL, com runner próprio e espelhos, três pontos mereceram atenção.
A configuração legada de migrações merecia revisão. O app.ini tinha:
[migrations]
ALLOW_LOCALNETWORKS = true
ALLOWED_DOMAINS = github.com,api.github.com,gitlab.com
Uma configuração explícita com a lista nova pode usar o modo estrito. Não é uma equivalência exata: entradas sem porta passam a permitir apenas 80 e 443. Além disso, github.com já cobre api.github.com:
[migrations]
EGRESS_MODE = strict
ALLOWED_HOST_LIST = github.com,gitlab.com,private,loopback
O exemplo permite redes privadas e loopback: remova esses presets se não forem necessários e prefira destinos específicos. Confira também de onde vêm os espelhos — no modo estrito, um Git interno numa porta como 3000 precisa de uma entrada com a porta, como 10.0.0.15/32:3000 (substitua pelo IP real do seu ambiente). Valide a configuração e o acesso aos destinos antes da virada.
O histórico de Actions seria parcialmente apagado. Com o padrão de 400 dias, execuções acima desse prazo seriam removidas na limpeza agendada. Definimos RUN_RETENTION_DAYS = 0 antes do upgrade.
O nginx não repassava o WebSocket. Depois do upgrade, o handshake em /-/ws respondia 101 Switching Protocols direto no Gitea, mas 426 Upgrade Required pelo nginx. O bloco de proxy até repassava os cabeçalhos Upgrade e Connection, mas faltava a versão do protocolo — no nginx 1.18.0 verificado, o padrão para o upstream é HTTP/1.0, que não permite esse upgrade:
Para uma configuração nova, o exemplo abaixo usa o map recomendado na documentação do nginx. O map pertence ao contexto http; o location, ao bloco server existente:
# Dentro de http {}, fora de server {}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Dentro do server {} existente
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1; # a linha que faltava
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Para testar, sem navegador:
curl -s --max-time 5 -o /dev/null -w '%{http_code}\n' --http1.1 \
-H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
https://git.exemplo.com.br/-/ws
# 101 = handshake concluído; 426 = investigar o upgrade no proxy
Depois de receber 101, a conexão permanece aberta; o timeout e o código de saída 28 do curl ao fim dos cinco segundos são esperados neste teste. Valide a configuração com nginx -t antes de recarregar o serviço.
As notificações continuam funcionando se você esquecer — a interface cai para consulta periódica —, mas as notificações deixam de ser instantâneas. Quem usa o Caddy na frente não precisa fazer nada: o reverse_proxy trata WebSocket sem configuração extra.
No proxy nginx 1.18.0 verificado, acrescentar proxy_http_version 1.1, mantendo os cabeçalhos de upgrade já existentes, mudou a resposta de 426 para 101. A página inicial continuou respondendo 200 depois da recarga.
Roteiro de upgrade
- Backup consistente: interrompa as gravações conforme a documentação de backup. Preserve banco, repositórios, configuração e arquivos de dados no mesmo ponto consistente; use o dump nativo do banco quando indicado e teste a restauração. A migração do banco não tem rollback automático.
- Compatibilidade: confira Git,
ROOT_URL, cadastro e workflows antes de reiniciar. - Retenção: decida o
RUN_RETENTION_DAYSantes de subir a 28. - Egress: se você usa
ALLOWED_DOMAINS,BLOCKED_DOMAINS,ALLOW_LOCALNETWORKSou o presetexternal, reescreva comALLOWED_HOST_LIST,BLOCKED_HOST_LISTeEGRESS_MODE. Se a lista precisa ser exclusiva, usestrict. - Scripts: ajuste o que monta URLs de download ou compara versões com “1.”.
- Troca: substitua o binário ou a tag da imagem (
docker.gitea.com/gitea:28.0.0) e reinicie. Confira a soma SHA-256 antes. - Verificação:
/api/healthz, a versão em/api/v1/version, os avisos no log de inicialização e o teste de WebSocket acima.
O passo a passo completo de instalação — Docker Compose com MariaDB, binário com systemd, runner e tea CLI — está no guia Gitea: Git self-hosted com Actions, runner e tea CLI, já atualizado para a 28.
Segurança
A 28 traz correções de segurança, entre elas a rejeição de objetos Git inválidos ou duplicados no push, a identificação de chaves SSH por impressão digital, a garantia de que execuções de PR vindas de fork continuem esperando aprovação mesmo depois de canceladas, uma correção de negação de serviço no SSH e a verificação de autorização por repositório em acessos de times, exclusões e pacotes. Os detalhes completos — com identificadores e gravidade — ficaram para cerca de uma semana depois do lançamento e, até 5 de outubro de 2026, ainda não tinham sido publicados. Isso, por si só, já é motivo para não adiar o upgrade.
Vale atualizar?
Sim, depois de revisar as mudanças incompatíveis e ter um backup restaurável. A 28 é uma release de amadurecimento: auditoria, bots, deploy tokens e aprovação por code owners ampliam os controles disponíveis para equipes, sem perder o que sempre foi sua vantagem — um binário, um banco e pouca memória. O número novo assusta mais do que a atualização.
Leia também: a história do GitLab, o Gogs, de onde o Gitea saiu e DevOps: o que é CI/CD?.
Referências: release 28.0.0, configurações do Gitea e runner 4.1.0. Revisado em 5 de outubro de 2026.