How to Use Certbot Standalone Mode with Let’s Encrypt on Ubuntu 20.04

Let’s Encrypt is a free and automated certificate authority operated by the nonprofit Internet Security Research Group (ISRG). Certbot’s standalone mode starts a temporary built-in web server on port 80 so it can complete the ACME HTTP-01 challenge provided by the Let’s Encrypt API. Standalone mode is suitable when your machine does not already run a web server or when you want to secure a non-HTTP service such as a mail server, MQTT broker, or custom TCP application. When Nginx, Apache, or another web server is already active, use the webroot plugin or the corresponding Nginx or Apache plugin instead so the verification can be completed without interrupting the live service.

In this tutorial, you will use EFF’s Certbot to request a valid Let’s Encrypt certificate with a 90-day validity period. The certificate will be stored in /etc/letsencrypt/live/your_domain/. You will also set up automatic renewal through Certbot’s systemd timer and configure pre-hook and post-hook scripts that stop and restart any service using port 80 whenever a renewal attempt takes place.

Note: This tutorial is intended for Ubuntu 20.04.

Key Takeaways

  • Certbot standalone mode launches a temporary internal web server that responds to the Let’s Encrypt ACME challenge. For the standard HTTP-01 challenge, TCP port 80 must be available.
  • Standalone mode is appropriate when no web server is active or when you are securing a non-HTTP service such as a mail server, message broker, or custom TCP application. If a web server already handles traffic, use webroot or the Nginx/Apache plugin instead.
  • On Ubuntu 20.04 and newer releases, Certbot should be installed through snap. The EFF no longer actively maintains the apt-based Certbot package for current Ubuntu versions.
  • Let’s Encrypt certificates remain valid for 90 days. Certbot installed through snap includes a systemd timer that checks for renewal twice per day and renews certificates that are within 30 days of expiration.
  • With standalone mode, port 80 must also be available whenever renewal occurs. Pre-hook and post-hook scripts can stop and restart the service using this port before and after each attempt.
  • Certificate files are located in /etc/letsencrypt/live/your_domain/. The two files required by most services are fullchain.pem, containing the certificate chain, and privkey.pem, containing the private key.

Prerequisites

Before beginning this tutorial, you will need:

  • An Ubuntu 20.04 server configured with a non-root user that has sudo privileges and a basic firewall.
  • A domain name that resolves to your server. This tutorial uses your_domain as the example domain.
  • Port 80 must be available on the server while the certificate is being issued and during every later renewal. If an existing web server currently uses port 80, stop that service before running Certbot in standalone mode and start it again afterward. If the service you are securing occupies both ports 80 and 443 and cannot be stopped temporarily, use Certbot’s webroot mode instead. UFW can be used to configure the required firewall rules on Ubuntu.

Step 1 — Installing Certbot

Begin by updating the snapd core. Ubuntu 20.04 provides snapd by default:

sudo snap install core; sudo snap refresh core

If your server previously had an older Certbot package installed, remove it before continuing:

Next, install Certbot through snap:

sudo snap install --classic certbot

Then create a link from the Certbot executable in the snap installation directory to your system path. This lets you run Certbot simply by entering certbot. This step is not required for every package, but snaps are generally designed to avoid interfering with other system packages:

sudo ln -s /snap/bin/certbot /usr/bin/certbot

Confirm that Certbot is installed by displaying its version:

Output:

The displayed version depends on the current snap channel when Certbot is installed and will change as newer Certbot versions are released.

Step 2 — Configuring the Firewall

Certbot standalone mode completes the ACME HTTP-01 challenge, which requires TCP port 80 to be accessible from the internet. Allow port 80 through the firewall:

Output:

Rule added
Rule added (v6)

Next, allow port 443 so HTTPS traffic can reach the server after the certificate has been issued:

Output:

Rule added
Rule added (v6)

Standalone mode uses the HTTP-01 challenge on port 80 by default. If you choose --preferred-challenges tls-alpn-01 instead, port 443 must be open. When port 80 is needed only for certificate issuance and your application does not provide HTTP service, you can close it afterward with sudo ufw delete allow 80.

Step 3 — Obtaining a Certificate with Standalone Mode

Note: Before requesting a certificate from the production Let’s Encrypt API, use --dry-run to validate your configuration. If you need to repeat the complete issuance process while testing, add --staging or --test-cert to avoid reaching rate limits. Certificates from the staging environment are not trusted by browsers, but the issuance workflow is otherwise the same. Consult the Let’s Encrypt rate limit documentation for current limits.

Warning: Standalone mode needs to bind to port 80. If Nginx, Apache, or another service already listens on port 80, stop that service before running the following command and start it again after the certificate has been created. Certbot will return a binding error when another process already occupies the port.

The --standalone option instructs Certbot to complete the challenge with its own temporary web server. The -d option identifies the domain for which you are requesting the certificate. Multiple -d options can be included when one certificate should cover several domains.

sudo certbot certonly --standalone -d your_domain

When the command runs, Certbot will request an email address and ask you to accept the terms of service. After completing these prompts, you should receive confirmation that the operation succeeded together with the location of the generated certificate files:

Output:

IMPORTANT NOTES:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your_domain/privkey.pem
This certificate expires on 2026-09-06.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le

Automating Certificate Issuance

For automated or scripted systems where interactive questions cannot be answered, provide the registration parameters directly in the command:

sudo certbot certonly --standalone \
  --non-interactive \
  --agree-tos \
  --no-eff-email \
  -m your_email@example.com \
  -d your_domain

--agree-tos accepts the Let’s Encrypt subscriber agreement, --no-eff-email disables the EFF newsletter registration prompt, and -m defines the email address Let’s Encrypt uses for expiration notices and renewal failure warnings. Provide a real email address because renewal failure messages may be the only advance warning when the systemd timer is no longer operating correctly.

The certificate files should now exist. List the directory containing the certificates and keys:

sudo ls /etc/letsencrypt/live/your_domain

Output:

cert.pem  chain.pem  fullchain.pem  privkey.pem  README

Check the certificate information with OpenSSL:

sudo openssl x509 -in /etc/letsencrypt/live/your_domain/cert.pem -noout -subject -issuer -dates -ext subjectAltName

Output:

subject=CN = your_domain
issuer=C = US, O = Let's Encrypt, CN = E5
notBefore=Mon Jun  8 00:00:00 2026 GMT
notAfter=Sun Sep  6 00:00:00 2026 GMT

X509v3 Subject Alternative Name:
    DNS:your_domain

The notAfter field verifies the 90-day certificate lifetime. The issuer field shows that the certificate was signed by Let’s Encrypt. When you request several domains by repeating the -d option, every covered domain appears as its own DNS: entry. Any domain that does not appear in this section is not included in the certificate.

To display every certificate currently maintained by Certbot together with the included domains and expiration dates, run:

Output:

Found the following certs:
  Certificate Name: your_domain
    Serial Number: ...
    Key Type: ECDSA
    Domains: your_domain
    Expiry Date: 2026-09-06 00:00:00+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/your_domain/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/your_domain/privkey.pem

This is the main Certbot command for reviewing certificate status on a server managed with Certbot.

Configuring a Service to Use the Certificate

Most services use two files from this directory: fullchain.pem for the certificate and privkey.pem for the private key. The following example contains the relevant TLS settings for Postfix in /etc/postfix/main.cf:

/etc/postfix/main.cf

smtpd_tls_cert_file = /etc/letsencrypt/live/your_domain/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/your_domain/privkey.pem
smtpd_tls_security_level = may

Note: The /etc/letsencrypt/live/ directory can normally be read only by root. Services running under a non-root account, such as the postfix user, require either a deploy hook that copies the certificate files to an accessible location or explicitly configured permissions for the certificate directory. A complete Postfix installation and configuration can be handled separately on Ubuntu 20.04.

For an MQTT broker such as Mosquitto, add the following configuration to /etc/mosquitto/conf.d/tls.conf:

/etc/mosquitto/conf.d/tls.conf

listener 8883
cafile /etc/ssl/certs/ca-certificates.crt
certfile /etc/letsencrypt/live/your_domain/fullchain.pem
keyfile /etc/letsencrypt/live/your_domain/privkey.pem

Port 8883 is the standard port for MQTT over TLS. The same restriction concerning root-only permissions applies. The mosquitto user therefore needs a deploy hook or explicit permission to read privkey.pem.

The README file located in the certificate directory contains additional information about each file. In most situations, only the following two files are needed:

  • privkey.pem: This is the private key and must remain secret. The /etc/letsencrypt directory is restricted to root for this reason. Services commonly reference this file through configuration options such as ssl-certificate-key or ssl-certificate-key-file.
  • fullchain.pem: This contains the complete certificate chain. Services commonly reference it through an option such as ssl-certificate.

For details about the remaining files, consult the “Where are my certificates?” section of the Certbot documentation.

Some applications require certificates in different formats, different directories, or with different user permissions. Whenever possible, keep the files inside the Let’s Encrypt directory and avoid modifying the permissions there because those permissions can be replaced during renewal. If this is not possible, create a script that copies or moves the required files and adjusts permissions as needed. The script must run every time Certbot renews the certificate, which is covered in the next step.

Step 4 — Configuring Automatic Renewal

Let’s Encrypt certificates have a 90-day lifetime. A snap-based Certbot installation creates a systemd timer named snap.certbot.renew.timer. This timer executes certbot renew twice each day and renews certificates when they reach the final 30 days before expiration. You do not need to configure a separate cron job.

Once a certificate has been renewed, Certbot must reload or restart the service that uses it so the service begins using the updated certificate files. With standalone mode, any process using port 80 must also be stopped before the renewal attempt and started again afterward. Certbot supports this through the renew_hook option in a renewal configuration file and through pre-hook and post-hook scripts stored in the renewal hook directories.

To configure a renew_hook, open the renewal configuration file for the domain:

sudo nano /etc/letsencrypt/renewal/your_domain.conf

The file contains the certificate renewal settings. Add a hook at the end of the file to reload the service that needs to use the renewed certificate:

/etc/letsencrypt/renewal/your_domain.conf

renew_hook = systemctl reload your_service

Replace the example command with the command required to reload your service or execute any certificate-processing script needed after renewal. On Ubuntu systems, systemctl is commonly used to reload services.

Using Pre-Hook and Post-Hook Scripts for Service Restarts

Every standalone renewal needs to bind to port 80 again so the ACME challenge can be completed. When another application occupies port 80 during renewal, the renewal attempt fails and the certificate can eventually expire. A pre-hook stops the application before Certbot performs the renewal attempt, while a post-hook starts it again afterward.

Consider the downtime involved before selecting this setup. Certbot must temporarily bind to port 80 for several seconds while completing the ACME challenge. A service stopped by the pre-hook remains unavailable during that period as well as during the time required to stop and restart it. This is generally acceptable for non-HTTP services such as mail, MQTT, and custom TCP applications. If renewal must occur without downtime, a reverse proxy can forward /.well-known/acme-challenge/ to Certbot’s --http-01-port while the main service remains available, but that configuration is outside the scope of this tutorial.

When choosing between the two hook methods below, use the renewal-hooks directory method in Approach 1 when the hooks should execute regardless of how renewal is initiated. Choose the inline flags method in Approach 2 when you want the hook settings stored directly in the renewal configuration during interactive certificate issuance. For standalone mode protecting a non-HTTP service, Approach 1 is the safer option.

Approach 1: Hook Scripts in the Renewal-Hooks Directories

Create the pre-hook script:

sudo tee /etc/letsencrypt/renewal-hooks/pre/stop-service.sh <<'EOF'
#!/bin/sh
systemctl stop your_service
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/pre/stop-service.sh

Create the post-hook script:

sudo tee /etc/letsencrypt/renewal-hooks/post/start-service.sh <<'EOF'
#!/bin/sh
systemctl start your_service
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/start-service.sh

Certbot executes scripts in these directories according to their position relative to the renewal process:

  • /etc/letsencrypt/renewal-hooks/pre/: Scripts in this directory run before every renewal attempt, whether or not a certificate is ultimately renewed.
  • /etc/letsencrypt/renewal-hooks/deploy/: Scripts in this directory run only after a certificate has been renewed successfully and deployed.
  • /etc/letsencrypt/renewal-hooks/post/: Scripts in this directory run after every renewal attempt, regardless of whether it succeeded.

Note: Replace your_service with the systemd unit used by your application. For example, a Postfix mail server uses postfix. To display active services and verify the appropriate unit name, run systemctl list-units --type=service.

Approach 2: Inline Flags During Certificate Issuance

sudo certbot certonly --standalone \
  --pre-hook "systemctl stop your_service" \
  --post-hook "systemctl start your_service" \
  -d your_domain

Certbot automatically records these settings in /etc/letsencrypt/renewal/your_domain.conf, allowing them to be applied during every later certbot renew execution.

Verifying the Renewal Timer

Confirm that the systemd timer is active and inspect the next scheduled execution:

sudo systemctl list-timers | grep certbot

Output:

NEXT                        LEFT     LAST                        PASSED   UNIT                       ACTIVATES
Tue 2026-06-09 12:00:00 UTC 5h left  Mon 2026-06-08 00:00:00 UTC 6h ago  snap.certbot.renew.timer   snap.certbot.renew.service

Perform a simulated renewal to confirm that the configuration works without requesting another production certificate:

sudo certbot renew --dry-run

Output:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)

If no errors appear, Certbot is configured to renew the certificate when required and to execute the commands needed for your service to start using the refreshed certificate files. Keep in mind that --dry-run executes pre-hooks and post-hooks but does not run deploy hooks. A renew_hook defined in the renewal configuration file also does not execute during a dry run because no real certificate is created.

Standalone Mode vs. Other Certbot Modes

Select the Certbot mode according to whether a web server is currently active and whether Certbot should automatically modify the web server configuration.

Mode Port Used Web Server Required Best for
--standalone 80 (HTTP-01) or 443 (TLS-ALPN-01) No, because Certbot uses its own built-in server Systems without an active web server and non-HTTP services such as mail, MQTT, or custom TCP applications
--webroot 80, served through the existing web server Yes, already running Websites handled by an existing web server that should not be stopped
--nginx 80/443 Yes, Nginx Nginx-hosted sites where Certbot should also modify the Nginx configuration automatically
--apache 80/443 Yes, Apache Apache-hosted sites where Certbot should also update the Apache configuration automatically
DNS-01 (--manual or DNS plugin) None, because verification uses a DNS TXT record No Wildcard certificates and servers without inbound HTTP or HTTPS access

If a web server is already active, use webroot or the appropriate Nginx or Apache plugin instead of standalone mode to avoid interrupting a live service during renewal. Nginx and Apache can each be secured with their corresponding Let’s Encrypt Certbot workflows on Ubuntu 20.04. For Ubuntu 22.04, use the standalone procedure intended for that Ubuntu release.

Troubleshooting Common Certbot Standalone Mode Issues

Port 80 Already in Use (EADDRINUSE)

If another program already occupies port 80, Certbot can fail with an OSError: [Errno 98] Address already in use message. Find the process currently using the port:

sudo ss -tlnp 'sport = :80'

Output:

State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1234,fd=6))

Stop the service using the port. The following example stops Nginx:

Run the certbot certonly --standalone command again. After the certificate has been issued, start the service:

sudo systemctl start nginx

Firewall Blocking Port 80

The ACME HTTP-01 challenge cannot succeed when Let’s Encrypt servers are unable to connect to port 80 on the public IP address associated with your domain. Review the current UFW configuration:

Output:

Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
443                        ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

If port 80 does not appear as allowed, add the firewall rule:

Output:

Rule added
Rule added (v6)

After certificate issuance, remove the rule when your application does not provide HTTP service:

Output:

Rule deleted
Rule deleted (v6)

IPv4/IPv6 Dual-Stack Binding Errors

In some Docker environments and virtual server configurations, Certbot may be unable to bind to port 80 because of conflicts between IPv4 and IPv6 dual-stack networking. The message commonly indicates that Certbot could not bind to IPv4 or IPv6 on port 80. Use --preferred-challenges to explicitly select the HTTP-01 challenge:

sudo certbot certonly --standalone --preferred-challenges http-01 -d your_domain

When Certbot runs inside a Docker container, make sure port 80 is mapped from the container to the host and verify that no process running directly on the host is already bound to port 80.

Challenge Fails with Port 80 Open Due to DNS or IPv6 Mismatch

If Certbot reports an error such as Connection refused or Timeout during connect even though UFW permits port 80, an incorrect DNS AAAA record can be responsible. Let’s Encrypt prefers IPv6 and attempts to reach the domain through IPv6 first. If an AAAA record points to an IPv6 address that does not actually provide access to port 80, verification fails even when IPv4 firewall rules are correct.

Check whether an AAAA record exists for the domain:

Output:

;; ANSWER SECTION:
your_domain.    300    IN    AAAA    2001:db8::1

If an AAAA record exists but IPv6 is not configured on the server, remove the AAAA record through your DNS provider or configure the server so its IPv6 address also listens on port 80. Check IPv6 port 80 availability with:

sudo ss -tlnp 'sport = :80'

Output:

State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1234,fd=6))
LISTEN 0      511             [::]:80            [::]:*     users:(("nginx",pid=1234,fd=6))

A dual-stack server should show both an IPv4 entry using 0.0.0.0:80 and an IPv6 entry using [::]:80. If only the IPv4 listener exists while the domain has an AAAA record, the Let’s Encrypt challenge will fail.

FAQ

What Is Certbot Standalone Mode and When Should I Use It?

Certbot standalone mode temporarily takes control of port 80 and makes a verification token available at a well-known URL. Let’s Encrypt retrieves this token to verify that you control the domain before issuing the certificate. Use standalone mode when no web server operates on the system or when you need to protect a non-HTTP service such as a mail server, MQTT broker, or custom TCP application.

Do I Need to Stop My Web Server Before Running Certbot in Standalone Mode?

Yes. Standalone mode requires Certbot to bind to port 80 while it handles the ACME challenge. If Nginx, Apache, or another web server is already using port 80, Certbot will fail with a binding error. Stop the web server before executing sudo certbot certonly --standalone and start it again after certificate issuance.

How Do I Renew a Let’s Encrypt Certificate Obtained with Standalone Mode?

When Certbot is installed through snap on Ubuntu 20.04, it registers the snap.certbot.renew.timer systemd timer, which executes certbot renew twice daily. In standalone mode, configure pre-hook and post-hook scripts to stop and restart any application that occupies port 80. You can define these hooks with --pre-hook and --post-hook or place executable scripts in /etc/letsencrypt/renewal-hooks/. Verify the setup with sudo certbot renew --dry-run.

Where Are the Certificate Files Stored After Certbot Issues Them?

Certbot stores the certificates in /etc/letsencrypt/live/your_domain/. The files commonly required by services include fullchain.pem, which contains the complete certificate chain; privkey.pem, which contains the private key; cert.pem, which contains only the end-entity certificate; and chain.pem, which contains only the intermediate chain.

What Happens If Port 80 Is Blocked by a Firewall During Certificate Issuance?

The ACME HTTP-01 validation fails because the Let’s Encrypt servers cannot connect to port 80 for your domain. Open the port using sudo ufw allow 80, execute the Certbot command, and then remove the firewall rule with sudo ufw delete allow 80 if your application does not serve HTTP traffic.

Can I Use Certbot Standalone Mode Inside a Docker Container?

Yes. However, port 80 must be forwarded from the container to the host, and no host-level application can use port 80 at the same time. IPv4 and IPv6 dual-stack networking can also produce binding problems in Docker environments. When this occurs, verify the container network and port mapping configuration or add --preferred-challenges http-01 to explicitly select the HTTP-01 challenge.

How Long Is a Let’s Encrypt Certificate Valid, and How Often Does It Renew?

A Let’s Encrypt certificate remains valid for 90 days. Certbot’s systemd timer attempts renewal when the certificate enters the final 30 days of its validity period. When the required pre-hooks and post-hooks are configured correctly and port 80 is available during renewal, no manual action is necessary.

Is Snap the Recommended Way to Install Certbot on Ubuntu 20.04?

Yes. The EFF recommends installing Certbot with snap on Ubuntu 20.04 and newer releases. The python3-certbot apt package is no longer actively maintained upstream. A snap installation also allows updated Certbot releases to be delivered automatically through the snap refresh system.

Conclusion

In this tutorial, you installed Certbot through snap on Ubuntu 20.04, configured the firewall so the ACME HTTP-01 challenge could use port 80 and HTTPS connections could use port 443, requested a certificate with certbot certonly --standalone, checked the certificate information with OpenSSL, and configured automatic renewal with Certbot’s systemd timer together with pre-hook and post-hook scripts.

The certificate files in /etc/letsencrypt/live/your_domain/ can now be used to configure TLS for services running on the server. The certificate can renew automatically without manual involvement as long as the configured pre-hooks and post-hooks ensure that port 80 is available for every renewal attempt.

Source: digitalocean.com

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: