SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor

Instalar Certbot no nginx do Ubuntu 24.04

Veja como instalar o Certbot via apt ou snap, emitir o certificado com certbot --nginx e evitar o erro de renovação por timeout na porta 80.

Instalar o Certbot: apt ou snap

No Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx instala um Certbot funcional que emite certificados Let's Encrypt reais e reconhecidos publicamente. A documentação oficial do Certbot recomenda usar o snap; a diferença é pequena: o snap acompanha as versões lançadas pelo projeto, enquanto o pacote do repositório acompanha a versão incluída no LTS e recebe correções de segurança.

Escolha uma opção. Duas cópias do Certbot significam dois temporizadores de renovação direcionados para a mesma árvore /etc/letsencrypt. A cópia que esqueceu será a que causará problemas.

O método com apt:

sudo apt update
sudo apt install certbot python3-certbot-nginx

Isso instala /usr/bin/certbot, o plugin do nginx, um par certbot.service + certbot.timer e uma entrada /etc/cron.d/certbot que não executa nenhuma ação sob o systemd.

O método com snap:

sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

O snap instala o seu próprio temporizador, snap.certbot.renew.timer. Remova o pacote apt antes de instalar o snap.

Depois disso, as duas instalações funcionam da mesma forma, exceto pelo momento exato em que decidem renovar os certificados (consulte a secção Renovação abaixo). Por predefinição, o Certbot 2.x usa chaves ECDSA (P-256). Use --key-type rsa apenas para um cliente que não consiga utilizar ECDSA. Todo o estado fica em /etc/letsencrypt: archive/ contém os ficheiros reais de chave e certificado, live/ aponta por symlink para os ficheiros atuais, renewal/ contém um ficheiro de configuração por certificado e accounts/ contém a chave da sua conta ACME.

O que o HTTP-01 realmente faz e por que a porta 80 não é opcional

O desafio HTTP-01 é um callback. Você solicita à Let's Encrypt um certificado que cubra example.com; o serviço resolve o nome no DNS público, abre uma ligação à porta 80 no endereço encontrado e solicita http://example.com/.well-known/acme-challenge/<token>. O seu servidor responde com o conteúdo exato do token que o Certbot acabou de gravar no disco. Esse é todo o mecanismo. Daqui resultam três consequências, que explicam a maioria das emissões que falham.

  • A porta 80 tem de estar acessível a partir da Internet pública, não apenas a partir do seu portátil. Uma regra ufw, um security group do fornecedor de cloud ou uma firewall da consola da VPS que abra apenas a porta 443 interrompe a emissão e todas as renovações futuras.
  • O DNS já tem de apontar para este servidor. O servidor de validação faz a sua própria consulta a partir do exterior; as suas entradas /etc/hosts e a cache do navegador não têm qualquer efeito.
  • Se publicar um registo AAAA, o IPv6 é tentado primeiro. A Let's Encrypt tenta novamente através de IPv4 apenas quando a ligação IPv6 excede o tempo limite. Uma ligação IPv6 recusada não é tentada novamente, e um AAAA obsoleto apontado para um host que aceita a ligação e serve outro conteúdo provoca uma falha definitiva.

Os redirecionamentos são permitidos: a validação segue um redirecionamento HTTP para HTTPS e não se importa que o certificado do outro lado esteja ausente, expirado ou seja autoassinado. O que não faz é começar noutro ponto que não seja a porta 80. O Certbot não tem uma implementação de TLS-ALPN-01, por isso “usar apenas a porta 443” não é uma alternativa.

Escolher um autenticador: --nginx, --webroot, --standalone

--nginx é a opção padrão correta quando o nginx já está em execução e já serve o domínio. O Certbot analisa a sua configuração, injeta temporariamente uma localização para o desafio, recarrega o nginx, valida o domínio e depois escreve as diretivas TLS no seu bloco de servidor. Não há indisponibilidade.

sudo certbot --nginx -d example.com -d www.example.com

Para execução por script, num servidor novo:

sudo certbot --nginx \
  -d example.com -d www.example.com \
  --agree-tos -m ops@example.com --no-eff-email \
  --redirect --non-interactive

--webroot é adequado quando não quer que o Certbot altere a configuração do nginx, por exemplo, quando esta é gerada a partir de um modelo, mantida no git ou aplicada com Ansible. O Certbot escreve apenas o ficheiro do desafio num diretório que já é servido pelo nginx.

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

--standalone é adequado quando não há nenhum serviço a escutar na porta 80: por exemplo, um servidor de correio, uma API que só aceita ligações na porta 443 ou um script de primeiro arranque executado antes de o nginx existir. O Certbot liga-se à porta 80 durante alguns segundos. Se o nginx estiver em execução, este método falha. Pare-o antes da execução e inicie-o depois:

sudo certbot certonly --standalone -d mail.example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

Esses hooks ficam registados na configuração de renovação do certificado. Assim, a mesma paragem e o mesmo arranque ocorrem automaticamente durante a renovação.

Um bloco de servidor que funciona antes e depois de o certificado existir

O problema do ovo e da galinha: o nginx recusa iniciar com ssl_certificate a apontar para um ficheiro que não existe, e o Certbot não consegue validar enquanto o nginx estiver parado. Disponibilize primeiro o site na porta 80.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Execute sudo nginx -t && sudo systemctl reload nginx, confirme que curl -I http://example.com/ responde a partir de fora do servidor e, depois, emita o certificado. Em seguida:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

O prefixo ACME mais longo já tem precedência sobre location /, pelo que return 301 nunca recebe um pedido de desafio. O modificador ^~ é útil noutro contexto: depois de esse prefixo corresponder, o nginx ignora completamente as localizações com expressões regulares. Assim, uma regra adicionada posteriormente, como location ~ /\. { deny all; }, não pode bloquear o desafio. Manter essa localização na porta 80 permite que as renovações continuem a funcionar depois de o resto do site passar a aceitar apenas HTTPS.

Ambos os blocos acima servem ficheiros do disco. Se o nginx estiver, em vez disso, à frente de uma aplicação, location / passa a ser um bloco proxy_pass, e o bloco de servidor do reverse proxy, linha a linha cobre os cabeçalhos necessários para essa aplicação. A localização ACME e as diretivas TLS permanecem exatamente iguais.

A sintaxe de HTTP/2 depende da versão do nginx. O Ubuntu 24.04 inclui o nginx 1.24, que exige a configuração inline, listen 443 ssl http2;, e falha ao iniciar se lhe fornecer a diretiva separada http2 on;. O Debian 13 inclui o nginx 1.26, que exige http2 on; e apenas avisa que a forma inline está obsoleta. Verifique nginx -v primeiro.

Aponte o nginx para live/, nunca para archive/. Os links simbólicos live/ são redirecionados em cada renovação. Um caminho direto para archive/ fixa a configuração num certificado que acaba por expirar.

Wildcards exigem DNS-01, e DNS-01 exige um plugin

Um certificado wildcard (*.example.com) não pode ser validado através de HTTP-01, porque não existe um único hostname de onde obter um ficheiro. DNS-01 é o único caminho: prova que controla o domínio ao publicar um registo TXT _acme-challenge.example.com. O Certbot precisa de credenciais de API do seu fornecedor de DNS para fazer isso sem intervenção, e é para esse fim que existem os plugins dos fornecedores. O guia completo para certificados wildcard explica o funcionamento dos registos TXT e o problema da renovação no modo manual; segue uma versão curta para Cloudflare.

sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

No caminho com apt, use sudo apt install python3-certbot-dns-cloudflare. Guarde as credenciais num ficheiro acessível apenas por root:

# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here

Limite o token a permissões para editar o DNS apenas nessa zona. É uma chave para o seu DNS; trate-a como tal.

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

Coloque o wildcard entre aspas para impedir que a shell faça a expansão de padrões. DNS-01 também resolve o que HTTP-01 não consegue: certificados para hosts sem a porta pública 80, um serviço interno, uma máquina acessível apenas através de uma VPN WireGuard auto-hospedada num VPS ou um painel de administração numa interface privada.

Renovação: os 90 dias, o temporizador e o deploy hook

Os certificados Let's Encrypt são válidos durante 90 dias. O pacote apt (Certbot 2.9.0) renova quando faltam menos de 30 dias. O snap acompanha a versão atual do Certbot, que desde a versão 4.1 também solicita à Let's Encrypt um período de renovação recomendado através de ACME Renewal Info (ARI) e pode renovar mais cedo; para um certificado Let's Encrypt, isso significa normalmente cerca de dois terços do período de validade. Em qualquer dos casos, fica com uma janela de aproximadamente 30 dias em que uma renovação com problemas ainda é um inconveniente corrigível, e não uma indisponibilidade. A partir do próximo ano, as renovações deverão ocorrer com mais frequência: o perfil predefinido da Let's Encrypt passa a emitir certificados de 64 dias em February 10, 2027 e certificados de 45 dias em February 16, 2028. A Let's Encrypt já não envia emails de aviso de expiração. Ninguém o vai avisar, por isso a monitorização fica agora a seu cargo.

Verifique o timer incluído na sua instalação:

systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificates

certbot renew percorre todas as configurações em /etc/letsencrypt/renewal/, ignora as que ainda não precisam de renovação e renova as restantes usando as opções guardadas nessa configuração. Essas são as flags da execução original, exceto se entretanto as tiver alterado com certbot reconfigure. É por isso que a primeira execução é importante: é a execução que fica registada.

Renovar o ficheiro no disco não altera nada por si só. O nginx continua a servir o certificado antigo que mantém em memória até receber uma instrução para recarregar. Configure um deploy hook uma vez:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Tudo o que for executável em renewal-hooks/deploy/ é executado depois de qualquer renovação bem-sucedida. A flag --deploy-hook faz o mesmo para um único certificado, guardando renew_hook = ... na respetiva configuração de renovação. certbot --nginx recarrega o serviço automaticamente; as configurações --webroot e --standalone não o fazem. A ausência de um hook é precisamente o motivo pelo qual um site pode servir um certificado expirado enquanto certbot certificates indica, sem problemas, que existe um certificado novo. Qualquer outro componente que leia o certificado no arranque precisa do mesmo hook. Uma aplicação contentorizada, como uma instalação do Nextcloud VPS com Docker, TLS e backups, também precisa que configure aqui o respetivo passo de reinício ou recarregamento.

Teste efetivo da renovação

sudo certbot renew --dry-run

Isto executa o desafio completo no ambiente de staging da Let's Encrypt: usa o mesmo caminho de código, a mesma firewall e o mesmo DNS. Nada é contabilizado nos limites de taxa de produção, porque o staging tem limites próprios e muito mais generosos. Não são guardados certificados no disco. Se passar hoje, a renovação automática daqui a 60 dias também passará, desde que nada no servidor seja alterado entretanto.

Um teste simulado não prova que o hook de reload é executado. O comportamento varia consoante a versão do Certbot. Teste essa parte manualmente: execute diretamente o script do hook, confirme que systemctl reload nginx é executado com sucesso e verifique sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.

Os erros que você vai encontrar na prática

Could not bind to IPv4 or IPv6., --standalone enquanto o nginx já mantém a porta 80 ocupada. Use --nginx ou --webroot, ou pare o nginx durante a execução. Confirme qual processo está a usar a porta com sudo ss -lntp | grep ':80'.

Timeout during connect (likely firewall problem), o Let's Encrypt não conseguiu chegar à porta 80. Verifique de dentro para fora: sudo ufw status (abra-a com sudo ufw allow 'Nginx Full'), depois a firewall do próprio fornecedor do VPS e, por fim, o DNS. Teste a partir de um local que não seja o seu servidor: curl -sSv http://example.com/.well-known/acme-challenge/test. Um registo AAAA desatualizado produz esta mesma mensagem.

unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, a porta 80 está acessível, mas o token não está a ser servido. O pedido chegou a um bloco de servidor diferente (verifique qual deles é responsável por default_server), ou o diretório passado a -w não é o diretório servido pelo nginx. Crie um ficheiro em /var/www/example.com/.well-known/acme-challenge/test e aceda a ele externamente; se devolver 404, o certificado nunca foi o problema.

DNS problem: NXDOMAIN looking up A for example.com, o nome não é resolvido publicamente. Pode tratar-se de registos novos que ainda não foram propagados ou de um registo numa zona que o seu registrador não está a servir.

too many certificates (5) already issued ..., um limite de frequência, que é o que muitas pessoas atingem ao repetir o processo de depuração. A mensagem identifica o limite e termina com um tempo retry after. O Let's Encrypt limita os certificados duplicados, com exatamente o mesmo conjunto de nomes, a cinco por semana, e não oferece substituição para esse limite. Por isso, só é necessário aguardar. Separadamente, permite 50 certificados novos por domínio registado por semana; esse limite pode ser aumentado através de um pedido de exceção, um pedido para exatamente o mesmo conjunto de nomes de um certificado existente conta como renovação e não está sujeito a esse limite, e as renovações coordenadas através de ARI não estão sujeitas a nenhum limite. Faça a depuração no ambiente de staging com --dry-run.

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, o nginx está configurado para um certificado que nunca foi emitido ou que foi removido com certbot delete. Comente o bloco de servidor TLS, inicie o nginx, emita o certificado e restaure o bloco.

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, esse ficheiro é instalado pelo pacote do plugin do nginx. Numa máquina certonly sem python3-certbot-nginx, instale o plugin ou substitua a linha include pelas suas próprias definições de ssl_protocols e ssl_ciphers.

Gerir isto em escala

Um certificado pode conter até 100 nomes, e um único certbot --nginx -d a.example.com -d b.example.com ... é tentador, até que um registo DNS obsoleto falhe a validação e faça todos os outros nomes desse certificado falharem também. Certificados separados por site falham de forma independente, que é o comportamento pretendido num servidor que aloja mais do que alguns serviços. Quando há mais do que alguns sites, uma entrada com suporte para ACME torna-se útil: um reverse proxy Traefik que executa várias aplicações com Docker Compose solicita e renova os certificados sozinho, e o Certbot deixa de ser necessário. A escolha do proxy para essa entrada é uma decisão própria, e comparar Nginx com Caddy e Traefik depende sobretudo da quantidade de trabalho de certificados e de configuração por aplicação que pretende delegar ao proxy.

Faça uma cópia integral de /etc/letsencrypt, incluindo sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt e mantendo os links simbólicos. Essa árvore contém accounts/, a chave da conta ACME, que não pode ser regenerada de forma idêntica. A migração para um novo VPS reduz-se então a: sincronizar a árvore com -a, instalar o Certbot, alterar o DNS e executar certbot renew --dry-run antes da mudança.

Se reconstruir o servidor ou mudar para uma nova versão LTS, o temporizador de renovação não é transferido. Depois de qualquer migração, restauração de snapshot ou atualização da distribuição, execute systemctl list-timers 'certbot*' e um --dry-run. Ignorar este passo pode deixar um site indisponível 89 dias mais tarde, às 3am, com um certificado que todos presumiam estar a ser renovado automaticamente.

As rotas HTTP-01 acima pressupõem uma máquina sob o seu controlo, com um IP público e a porta 80 aberta para a Internet, ou seja, um VPS; o DNS-01 não precisa de nenhum desses requisitos. O procedimento acima é idêntico em qualquer uma das opções.

Os mesmos passos para certificados aplicam-se ao Apache em vez do nginx, e, quando não é possível usar um certificado público, um certificado autoassinado no Ubuntu é suficiente para serviços internos.

FAQ

Preciso de manter a porta 80 aberta se o meu site só servir HTTPS?

Sim, para o desafio HTTP-01. O Let's Encrypt inicia sempre o pedido de validação na porta 80, e o Certbot não tem uma implementação de TLS-ALPN-01. Por isso, uma firewall que abra apenas a porta 443 bloqueia tanto a primeira emissão como todas as renovações automáticas posteriores. Um redirecionamento da porta 80 para HTTPS é aceitável; a validação segue-o. A única forma de excluir completamente a porta 80 é usar DNS-01, através de um plugin do fornecedor ou de --manual --preferred-challenges dns do Certbot. Nesse caso, adicione manualmente o registo TXT, e a renovação deixa de ser automática, a menos que forneça um script --manual-auth-hook.

apt ou snap: qual devo instalar para usar o Certbot com nginx no Ubuntu 24.04?

Use apt. sudo apt install certbot python3-certbot-nginx fornece o Certbot 2.9.0 no Ubuntu 24.04. Essa versão é suficientemente atual para tudo o que é apresentado neste guia, recebe correções de segurança através de unattended-upgrades e não requer snapd. Escolha snap apenas se precisar imediatamente da versão mais recente ou de um plugin DNS distribuído exclusivamente como snap. Em qualquer caso, escolha exatamente um: duas instalações significam dois temporizadores de renovação apontados para a mesma árvore /etc/letsencrypt, e a instalação esquecida é a que causa problemas.

O Certbot pode emitir um certificado wildcard para nginx?

Apenas através de DNS-01. Um wildcard como *.example.com não tem um nome de anfitrião único do qual obter um ficheiro de desafio. Por isso, --nginx, --webroot e --standalone não são opções. Instale o plugin do seu fornecedor DNS, coloque um token de API com permissões limitadas num ficheiro de credenciais acessível apenas por root e execute certbot certonly --dns-cloudflare -d example.com -d '*.example.com', colocando o wildcard entre aspas para impedir que o shell o interprete como um padrão global.

Porque é que o nginx continua a servir o certificado antigo depois de uma renovação bem-sucedida?

O nginx mantém o certificado em memória e não deteta o novo ficheiro no disco até ser recarregado. certbot --nginx faz o recarregamento automaticamente, mas as execuções de --webroot e --standalone não o fazem. Assim, uma renovação pode ser concluída com sucesso enquanto o navegador continua a receber um certificado prestes a expirar. Coloque um script executável em /etc/letsencrypt/renewal-hooks/deploy/ que execute nginx -t && systemctl reload nginx. O script será executado depois de cada renovação bem-sucedida.

certbot renew --dry-run prova que a renovação vai funcionar?

Na maioria dos casos. O comando executa o desafio real no ambiente de staging, com a mesma firewall, o mesmo DNS e o mesmo caminho de execução, sem afetar os limites de pedidos do ambiente de produção e sem guardar certificados no disco. Por isso, uma execução bem-sucedida indica que a parte de rede está correta. No entanto, não prova de forma fiável que o deploy hook é executado. Teste-o separadamente: execute o script do hook manualmente e verifique sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.