Issuance and renewal

Certbot "Could not bind TCP port 80": standalone plugin fix

By · Last verified

Error messageCould not bind TCP port 80

"Could not bind TCP port 80" means Certbot's standalone plugin tried to start a temporary web server on port 80 for the HTTP-01 challenge and the operating system refused. Usually your web server already listens on port 80; less often Certbot lacks root privileges. The fix is local; your domain and Let's Encrypt are fine.

The two versions of the message

The message ends in one of two ways, and the ending tells you the cause:

Could not bind TCP port 80 because it is already in use by another process on this
system (such as a web server). Please stop the program in question and then try again.
Could not bind TCP port 80 because you don't have the appropriate permissions (for
example, you aren't running this program as root).

The first one comes with a Retry or Cancel prompt in interactive runs. Logs may also show the underlying Problem binding to port 80: ... error.

Why port 80 at all

Standalone mode needs port 80 because Let's Encrypt performs HTTP-01 validation only on that port. With HTTP-01, the CA fetches http://example.com/.well-known/acme-challenge/<token>. The Let's Encrypt documentation states that the HTTP-01 challenge can only be done on port 80. The standalone plugin answers that request with its own server, and Certbot's guide notes that it needs to bind to port 80, "so you may need to stop your existing webserver".

Where you meet it

The message appears in three situations: a first install with --standalone, an automatic renewal, or a container setup. During a first installation, someone follows a guide that uses certbot certonly --standalone on a server that already runs a web server. During automatic renewal, the systemd timer or cron job runs certbot renew with the standalone settings saved months earlier, the run fails quietly, and the problem surfaces only when the certificate is close to expiry; Let's Encrypt stopped sending expiry reminder emails on 2025-06-04, so nothing warns you. And in containers, Certbot runs in one container while a reverse proxy in another already publishes port 80 on the host.

Common causes, most frequent first

1. A web server is already running

Nginx, Apache, Caddy or a control panel's web server holds port 80. Find it:

sudo ss -ltnp 'sport = :80'

2. Renewal saved the standalone plugin

The first certificate was issued before a web server existed. Certbot stores the authenticator in /etc/letsencrypt/renewal/example.com.conf; when the renewal timer runs months later, port 80 is busy. Check:

sudo grep -E 'authenticator|pre_hook|post_hook' /etc/letsencrypt/renewal/example.com.conf

3. A container publishes port 80

docker ps shows 0.0.0.0:80->80/tcp for a reverse proxy container. Certbot on the host cannot bind the same port. See Docker, Nginx and Certbot for a webroot-based setup.

4. Certbot runs without root

Running as a normal user, or in a container without the capability to bind low ports, triggers the permission variant. Use sudo, or run Certbot where it has those privileges.

5. A stuck earlier run

An interrupted Certbot process may still hold the port. ss shows a certbot or python process; stop it and retry.

How to fix it

Switching to the webroot, Nginx or Apache plugin is the cleanest fix; stopping the web server briefly or using DNS-01 also works.

Option A: use the web server instead of standalone (recommended).

With Nginx or Apache installed, let Certbot use them:

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

Or keep the web server running and use webroot, serving challenge files from a directory:

sudo certbot certonly --webroot -w /var/www/example.com -d example.com -d www.example.com

To switch an existing certificate without reissuing it by hand, Certbot 2.3.0 and newer provide reconfigure, which runs a test renewal against the staging server and saves the new options only if it succeeds:

sudo certbot reconfigure --cert-name example.com --webroot -w /var/www/example.com

Option B: keep standalone and stop the web server briefly.

sudo certbot renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"

Certbot's guide explains that these hooks run before and after each renewal attempt. Executables in /etc/letsencrypt/renewal-hooks/pre and /post run automatically as well; see Certbot renewal hooks.

Option C: use DNS-01. No inbound port is needed, and wildcards become possible.

Permission variant: run with sudo. Do not change port 80 permissions system-wide.

What not to do

  • Do not kill whatever holds port 80 without checking. On a shared server or hosting panel, that process may serve other sites; stop it through its service manager so it comes back cleanly.
  • Do not move the challenge to another port with --http-01-port unless external port 80 is forwarded to it; the CA will still connect to port 80.
  • Do not grant the Python interpreter the right to bind low ports to avoid running Certbot as root; that capability would apply to every script using the same interpreter.
  • Do not disable automatic renewal to silence failing runs. Fix the plugin choice, then confirm with a dry run.

How to verify the fix

  1. Test the full flow against the staging environment without touching your real certificate:

bash sudo certbot renew --dry-run

  1. sudo certbot certificates lists the certificate with its new expiry date after a real run.
  2. Check that the web server is running again (systemctl status nginx) and that the site serves the new certificate with the SSL checker.

FAQ

Can I make Certbot validate on another port, like 8080?

Let's Encrypt only connects to port 80 for HTTP-01. --http-01-port 8080 makes Certbot listen on 8080, which helps only if something forwards external port 80 to it, such as a NAT rule or a reverse proxy. Without that, validation fails with a connection error.

Renewal worked for months and now fails with this error. Why?

The certificate was first issued with standalone while nothing used port 80, and Certbot saved that choice in its renewal configuration. Since then a web server was installed. Switch the certificate to webroot or the web server plugin, or add pre and post hooks.

Is stopping Nginx during renewal acceptable?

It works, but the site is down for the few seconds Certbot needs. Webroot or the Nginx plugin avoid any downtime, so prefer them for production sites.

What if I cannot use port 80 at all?

Use the DNS-01 challenge, which proves control through a TXT record and needs no inbound port. It is also the only way to get wildcard certificates; see Certbot DNS validation with Cloudflare.

Sources

  1. Certbot source: standalone plugin
  2. Certbot User Guide
  3. Let's Encrypt: Challenge Types
  4. RFC 8555 section 8.3: HTTP Challenge