SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-23

Instalar Certbot para nginx en Ubuntu 24.04

Instala Certbot con apt o snap en Ubuntu 24.04, configura nginx y evita errores de renovación como el timeout del puerto 80 con Let's Encrypt.

Instalar Certbot: apt o snap

En Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx proporciona un Certbot funcional que emite certificados reales de Let's Encrypt con confianza pública. La documentación original de Certbot recomienda usar snap; la diferencia es limitada: snap sigue las versiones originales, mientras que el paquete del archivo sigue lo que se incluyó con la LTS y recibe actualizaciones de seguridad.

Elija una opción. Tener dos copias de Certbot implica dos temporizadores de renovación dirigidos al mismo árbol /etc/letsencrypt. La copia que olvidó será la que le cause problemas.

La opción con apt:

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

Esto instala /usr/bin/certbot, el plugin de nginx, un par certbot.service + certbot.timer y una entrada /etc/cron.d/certbot que no hace nada bajo systemd.

La opción con 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

El snap incluye su propio temporizador, snap.certbot.renew.timer. Elimine el paquete de apt antes de instalar el snap.

Después, ambas instalaciones funcionan igual. Certbot 2.x usa de forma predeterminada claves ECDSA (P-256). Use --key-type rsa sólo para un cliente que no admita ECDSA. Todo el estado se encuentra en /etc/letsencrypt: archive/ contiene los archivos reales de claves y certificados, live/ apunta mediante enlaces simbólicos a los archivos actuales, renewal/ contiene un archivo de configuración por certificado y accounts/ contiene la clave de su cuenta de ACME.

Qué hace realmente HTTP-01 y por qué el puerto 80 no es opcional

El desafío HTTP-01 es una devolución de llamada. Solicita a Let's Encrypt un certificado para example.com; Let's Encrypt resuelve el nombre mediante el DNS público, abre una conexión al puerto 80 en la dirección que encuentra y solicita http://example.com/.well-known/acme-challenge/<token>. Su servidor responde con el contenido exacto del token que Certbot acaba de escribir en disco. Ese es todo el mecanismo. De ahí se derivan tres consecuencias que explican la mayoría de las emisiones fallidas.

  • El puerto 80 debe ser accesible desde Internet público, no sólo desde su portátil. Una regla de ufw, un grupo de seguridad del proveedor cloud o un firewall de la consola del VPS que abra sólo el puerto 443 interrumpe la emisión y todas las renovaciones posteriores.
  • El DNS ya debe apuntar a este servidor. El servidor de validación realiza su propia consulta desde el exterior; sus entradas /etc/hosts y la caché del navegador no le sirven.
  • Si publica un registro AAAA, primero se intenta usar IPv6. Let's Encrypt reintenta mediante IPv4 cuando la conexión IPv6 falla directamente, pero un registro AAAA obsoleto que apunta a un host que acepta la conexión y sirve otro contenido provoca un fallo definitivo.

Se permiten las redirecciones: la validación sigue una redirección HTTP a HTTPS y no tiene en cuenta que el certificado del otro extremo falte, esté caducado o sea autofirmado. Lo que no hará es iniciar la validación en un puerto distinto del 80. Certbot no implementa TLS-ALPN-01, por lo que «usar sólo 443» no es una alternativa.

Elegir un autenticador: --nginx, --webroot, --standalone

--nginx es la opción predeterminada adecuada cuando nginx ya está en ejecución y ya sirve el dominio. Certbot analiza la configuración, inserta una ubicación temporal para el desafío, recarga nginx, valida el dominio y después escribe las directivas TLS en el bloque de servidor. No hay tiempo de inactividad.

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

Para usarlo mediante un script en un sistema recién instalado:

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

--webroot es adecuada cuando no quiere que Certbot modifique la configuración de nginx. Puede generar esa configuración a partir de una plantilla, mantenerla en git o distribuirla con Ansible. Certbot sólo escribe el archivo del desafío en un directorio que ya está sirviendo.

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

--standalone es adecuada cuando ningún servicio escucha en el puerto 80: por ejemplo, un servidor de correo, una API que sólo usa 443 o un script de primera ejecución que se ejecuta antes de que exista nginx. Certbot enlaza el puerto 80 durante unos segundos. Si nginx está en ejecución, el proceso falla. Deténgalo durante la ejecución:

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

Esos hooks se registran en la configuración de renovación del certificado, por lo que la misma detención y el mismo arranque se realizan automáticamente durante la renovación.

Un bloque de servidor que funciona antes y después de que exista el certificado

El problema del huevo y la gallina es el siguiente: nginx se niega a iniciar si ssl_certificate apunta a un archivo que no existe, y Certbot no puede validar el dominio mientras nginx está detenido. Primero, ponga el sitio en funcionamiento en el puerto 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;
    }
}

Ejecute sudo nginx -t && sudo systemctl reload nginx, confirme desde fuera del servidor que curl -I http://example.com/ responde y, después, emita el certificado. A continuación:

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;
    }
}

El prefijo ^~ de la ubicación de ACME resulta necesario: evita que el bloque return 301 intercepte la solicitud de desafío. Mantener esa ubicación en el puerto 80 permite que las renovaciones sigan funcionando después de que el resto del sitio pase a usar exclusivamente HTTPS.

Los dos bloques anteriores sirven archivos desde el disco. Si nginx está delante de una aplicación, location / se convierte en un bloque proxy_pass y el bloque de servidor del reverse proxy, línea por línea explica las cabeceras que necesita esa aplicación. La ubicación de ACME y las directivas TLS permanecen exactamente igual.

La sintaxis de HTTP/2 depende de la versión de nginx. Mezclar las dos formas provoca un error durante el arranque. Ubuntu 24.04 incluye nginx 1.24, que requiere la opción en la misma línea: listen 443 ssl http2;. Debian 13 incluye una versión más reciente de nginx, que requiere la directiva http2 on; por separado. Compruébelo primero con nginx -v.

Haga que nginx apunte a live/, nunca a archive/. Los enlaces simbólicos de live/ se vuelven a apuntar durante cada renovación. Una ruta fija dentro de archive/ hace que el servidor siga usando un certificado que acabará caducando.

Los comodines requieren DNS-01, y DNS-01 requiere un plugin

Un certificado comodín (*.example.com) no se puede validar mediante HTTP-01 porque no existe un nombre de host único desde el que obtener un archivo. DNS-01 es la única opción: demuestra que controla el dominio publicando un registro TXT _acme-challenge.example.com. Certbot necesita credenciales de API del proveedor de DNS para hacerlo sin intervención, y para eso existen los plugins del proveedor. La guía completa para certificados comodín explica el funcionamiento de los registros TXT y el problema de renovación del modo manual; a continuación se muestra la versión breve para Cloudflare.

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

En la ruta de apt se usa sudo apt install python3-certbot-dns-cloudflare en su lugar. Las credenciales se guardan en un archivo accesible sólo por root:

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

Limite el token a los permisos de edición de DNS en esa única zona. Es una clave para su DNS; trátela como tal.

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

Entrecomille el comodín para que el shell no lo expanda. DNS-01 también resuelve lo que HTTP-01 no puede: certificados para hosts sin el puerto público 80, un servicio interno, un equipo accesible sólo mediante una VPN WireGuard autohospedada en un VPS o un panel de administración en una interfaz privada.

Renovación: los 90 días, el temporizador y el deploy hook

Los certificados de Let's Encrypt son válidos durante 90 días. Certbot renueva cuando quedan menos de 30 días de validez. Esto proporciona una ventana de 30 días en la que una renovación fallida es un problema que se puede corregir, no una interrupción del servicio. Let's Encrypt ya no envía correos de advertencia de expiración. Nadie le avisará, así que ahora debe encargarse usted de la monitorización.

Compruebe el temporizador que instaló su sistema:

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

certbot renew recorre todas las configuraciones de /etc/letsencrypt/renewal/, omite las que están fuera de la ventana de 30 días y renueva las demás usando exactamente las opciones de la ejecución original. Por eso la primera ejecución es importante: es la que queda registrada.

Renovar el archivo en disco no cambia nada por sí solo. nginx sigue sirviendo el certificado antiguo desde la memoria hasta que algo le indica que debe recargarlo. Configure un deploy hook una sola 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

Todo ejecutable de renewal-hooks/deploy/ se ejecuta después de cualquier renovación correcta. La opción --deploy-hook hace lo mismo para un certificado, y guarda renew_hook = ... en su configuración de renovación. certbot --nginx realiza la recarga automáticamente; las configuraciones --webroot y --standalone no lo hacen. La ausencia del hook explica exactamente por qué un sitio sirve un certificado expirado mientras certbot certificates informa correctamente de que hay uno nuevo. Cualquier otro componente que lea el certificado durante el arranque necesita el mismo hook. Una aplicación en contenedores, como una instalación de Nextcloud VPS con Docker, TLS y copias de seguridad, también necesita incluir aquí su propio paso de reinicio o recarga.

Pruebas reales de renovación

sudo certbot renew --dry-run

Esto ejecuta el desafío completo contra el entorno de pruebas de Let's Encrypt: la misma ruta de código, el mismo firewall y el mismo DNS, sin consumir límites de frecuencia ni escribir nada en el disco. Si hoy se ejecuta correctamente, la renovación desatendida dentro de 60 días también funcionará, siempre que no cambie nada en el servidor mientras tanto.

Una ejecución de prueba no demuestra que se ejecute el hook de recarga. Este comportamiento varía según la versión de Certbot. Pruebe esa parte manualmente: ejecute directamente el script del hook, confirme que systemctl reload nginx se ejecuta correctamente y compruebe sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.

Los errores que encontrará realmente

Could not bind to IPv4 or IPv6., --standalone mientras nginx ya ocupa el puerto 80. Use --nginx o --webroot, o detenga nginx durante la ejecución. Confirme qué proceso lo ocupa con sudo ss -lntp | grep ':80'.

Timeout during connect (likely firewall problem), Let's Encrypt no pudo acceder al puerto 80. Compruebe la ruta hacia fuera: sudo ufw status (ábralo con sudo ufw allow 'Nginx Full'), después el firewall del propio proveedor del VPS y, por último, DNS. Pruebe desde un sistema que no sea el servidor: curl -sSv http://example.com/.well-known/acme-challenge/test. Un registro AAAA obsoleto produce el mismo mensaje.

unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, el puerto 80 es accesible, pero el token no se está sirviendo. La solicitud llegó a otro bloque de servidor (compruebe cuál es el que gestiona default_server), o el directorio pasado a -w no es el que nginx sirve. Cree un archivo en /var/www/example.com/.well-known/acme-challenge/test y solicítelo desde el exterior; si devuelve 404, el certificado nunca fue el problema.

DNS problem: NXDOMAIN looking up A for example.com, el nombre no se resuelve públicamente. Puede deberse a registros nuevos que aún no se han propagado o a un registro de una zona que su registrador no está sirviendo.

too many certificates already issued for: example.com, un límite de tasa, y el que más se alcanza al depurar en bucle. Let's Encrypt limita los certificados duplicados, con exactamente el mismo conjunto de nombres, a cinco por semana. Por separado, permite 50 certificados nuevos por dominio registrado y por semana. Nada elimina ninguno de los dos límites salvo esperar. Depure contra el entorno de staging con --dry-run.

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx está configurado para un certificado que nunca se emitió o que se eliminó con certbot delete. Comente el bloque de servidor TLS, inicie nginx, emita el certificado y restaure el bloque.

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, ese archivo se incluye en el paquete del plugin de nginx. En un sistema certonly sin python3-certbot-nginx, añada el plugin o sustituya la línea include por sus propios ajustes de ssl_protocols y ssl_ciphers.

Administrar esto a escala

Un certificado puede incluir hasta 100 nombres, y un único certbot --nginx -d a.example.com -d b.example.com ... resulta tentador. Sin embargo, un registro DNS obsoleto puede fallar durante la validación y hacer que todos los demás nombres de ese certificado fallen también. Los certificados separados por sitio fallan de forma independiente. Eso es lo que necesita un servidor que aloja más de un par de servicios. Cuando administra varios sitios, una puerta de entrada compatible con ACME resulta útil: un proxy inverso Traefik que ejecuta varias aplicaciones con Docker Compose solicita y renueva los certificados por sí mismo, y Certbot deja de ser necesario. Elegir el proxy para esa puerta de entrada es una decisión independiente. Comparar Nginx con Caddy y Traefik depende principalmente de cuánto trabajo de certificados y configuración por aplicación desea delegar al proxy.

Haga una copia de seguridad completa de /etc/letsencrypt, con sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt y los enlaces simbólicos intactos. Ese árbol contiene accounts/, la clave de cuenta de ACME, que no puede regenerar de forma idéntica. Migrar a un VPS nuevo se reduce entonces a lo siguiente: sincronizar el árbol con -a, instalar Certbot, actualizar la dirección DNS y ejecutar certbot renew --dry-run antes de cambiar el tráfico.

Si reconstruye el servidor o cambia a una versión LTS nueva, el temporizador de renovación no se traslada automáticamente. Después de cualquier migración, restauración de una instantánea o actualización de la distribución, ejecute systemctl list-timers 'certbot*' y un --dry-run. Omitir este paso puede dejar un sitio fuera de servicio 89 días después, a las 3am, porque todos suponían que el certificado se renovaba automáticamente.

Todo esto presupone una máquina bajo su control, con una IP pública y el puerto 80 abierto a Internet. En otras palabras, un VPS. El procedimiento es el mismo en cualquiera de estos casos.

Los mismos pasos para certificados se aplican en Apache en lugar de nginx. Cuando no es posible usar un certificado público, un certificado autofirmado en Ubuntu permite proteger los servicios internos.

FAQ

¿Necesito abrir el puerto 80 si mi sitio sólo ofrece HTTPS?

Sí, para el desafío HTTP-01. Let’s Encrypt siempre inicia la solicitud de validación en el puerto 80 y Certbot no tiene una implementación de TLS-ALPN-01. Por tanto, un firewall que sólo permita el puerto 443 bloquea tanto la primera emisión como todas las renovaciones desatendidas posteriores. Una redirección del puerto 80 a HTTPS es válida; la validación la sigue. La única forma de omitir por completo el puerto 80 es usar DNS-01 con un plugin del proveedor.

¿apt o snap? ¿Qué Certbot debo instalar para nginx en Ubuntu 24.04?

Use apt. sudo apt install certbot python3-certbot-nginx le proporciona Certbot 2.9.0 en Ubuntu 24.04, una versión suficientemente actual para todo lo descrito en esta guía. También recibe parches de seguridad mediante unattended-upgrades y no requiere snapd. Elija snap sólo si necesita inmediatamente la versión más reciente o un plugin DNS distribuido exclusivamente como snap. En cualquier caso, elija exactamente uno: dos instalaciones implican dos temporizadores de renovación apuntando al mismo árbol /etc/letsencrypt, y la instalación olvidada es la que termina causando problemas.

¿Puede Certbot emitir un certificado comodín para nginx?

Sólo mediante DNS-01. Un comodín como *.example.com no tiene un nombre de host único desde el que se pueda obtener un archivo de desafío, por lo que --nginx, --webroot y --standalone quedan descartados. Instale el plugin de su proveedor DNS, coloque un token de API con permisos limitados en un archivo de credenciales accesible sólo por root y ejecute certbot certonly --dns-cloudflare -d example.com -d '*.example.com' entre comillas para impedir que el shell lo interprete como un patrón global.

¿Por qué nginx sigue sirviendo el certificado antiguo después de una renovación correcta?

nginx mantiene el certificado en memoria y no detecta el archivo nuevo en disco hasta que se recarga. certbot --nginx realiza la recarga automáticamente, pero --webroot y --standalone no lo hacen. Por eso, una renovación puede completarse correctamente mientras el navegador sigue viendo un certificado próximo a caducar. Coloque un script ejecutable en /etc/letsencrypt/renewal-hooks/deploy/ que ejecute nginx -t && systemctl reload nginx; se ejecutará después de cada renovación correcta.

¿certbot renew --dry-run demuestra que la renovación funcionará?

En gran medida. Ejecuta el desafío real contra el entorno de staging, con el mismo firewall, el mismo DNS y la misma ruta de código, sin consumir límites de solicitudes ni escribir nada en disco. Por tanto, si se completa correctamente, la parte de red funciona. Sin embargo, no demuestra de forma fiable que se ejecute el deploy hook. Pruebe esa parte por separado: ejecute manualmente el script del hook y compruebe sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.