When I first set up HTTPS on my server, I realized I did not fully understand what was happening behind the scenes. I knew I needed a certificate, and I knew Let’s Encrypt was free, but the details were fuzzy. So I dug into it.

What TLS actually does

TLS (Transport Layer Security) provides three things for a connection between a client and a server:

  • Encryption. The data in transit cannot be read by anyone observing the network.
  • Authentication. The client can verify it is talking to the intended server, not an impostor.
  • Integrity. The data cannot be modified in transit without detection.

When your browser connects to a site over HTTPS, a TLS handshake happens before any HTTP data is exchanged. During this handshake, the server presents its certificate.

How certificates work

A TLS certificate is a file that binds a public key to a domain name. It is signed by a Certificate Authority (CA), which is an organization that browsers trust.

The chain of trust looks like this:

  1. Your server has a certificate for packetlog.org, signed by Let’s Encrypt.
  2. Let’s Encrypt’s intermediate certificate is signed by ISRG Root X1.
  3. ISRG Root X1 is in your browser’s trust store.

When a browser connects, it walks this chain from your certificate up to a root it trusts. If the chain is valid and the domain matches, the connection proceeds.

The certificate contains:

  • The domain name (or names) it is valid for
  • The public key
  • The validity period (usually 90 days for Let’s Encrypt)
  • The issuer’s signature

Let’s Encrypt and ACME

Let’s Encrypt is a free, automated CA. It uses the ACME protocol (Automatic Certificate Management Environment) to verify that you control a domain before issuing a certificate.

The most common verification method is the HTTP-01 challenge:

  1. You request a certificate for packetlog.org.
  2. Let’s Encrypt gives you a token.
  3. You place that token at http://packetlog.org/.well-known/acme-challenge/<token>.
  4. Let’s Encrypt fetches that URL. If it gets the expected response, it issues the certificate.

There is also the DNS-01 challenge, which requires creating a TXT record. This is useful for wildcard certificates or situations where port 80 is not available.

Installing Certbot

Certbot is the standard client for Let’s Encrypt. On Debian/Ubuntu:

apt install certbot python3-certbot-nginx

The python3-certbot-nginx plugin allows Certbot to automatically configure Nginx.

Getting your first certificate

Before running Certbot, make sure:

  • Your domain’s DNS A record points to your server’s IP.
  • Nginx is running and serving your domain on port 80.
  • Port 80 is open in your firewall.

A basic Nginx config to start with:

server {
    listen 80;
    server_name packetlog.org;
    root /var/www/packetlog.org;
}

Then run Certbot:

certbot --nginx -d packetlog.org

Certbot will:

  1. Verify domain ownership via the HTTP-01 challenge.
  2. Obtain the certificate.
  3. Modify your Nginx configuration to enable HTTPS.
  4. Set up a redirect from HTTP to HTTPS.

After completion, your Nginx config will have new blocks for port 443 with the certificate paths filled in.

What Certbot adds to Nginx

After running Certbot, the Nginx configuration gains something like this:

server {
    listen 443 ssl;
    server_name packetlog.org;
    root /var/www/packetlog.org;

    ssl_certificate /etc/letsencrypt/live/packetlog.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/packetlog.org/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

server {
    listen 80;
    server_name packetlog.org;
    return 301 https://$server_name$request_uri;
}

The fullchain.pem file contains your certificate plus the intermediate certificate. The privkey.pem is your private key.

Automatic renewal

Let’s Encrypt certificates expire after 90 days. Certbot installs a systemd timer (or cron job, depending on your system) that attempts renewal twice a day:

systemctl list-timers | grep certbot

You can test the renewal process without actually renewing:

certbot renew --dry-run

If this succeeds, your renewal is properly configured and will happen automatically.

Checking your certificate

After setup, verify that everything is working. From any machine:

openssl s_client -connect packetlog.org:443 -servername packetlog.org < /dev/null 2>/dev/null | openssl x509 -noout -dates

This shows the validity dates of the certificate. You can also check the full chain:

openssl s_client -connect packetlog.org:443 -servername packetlog.org -showcerts < /dev/null

Or use an online tool like the SSL Labs test for a thorough analysis.

Common issues

A few things I ran into or have seen others encounter:

Port 80 blocked. The HTTP-01 challenge requires port 80 to be reachable. If your firewall blocks it, Certbot will fail with a connection timeout. Make sure UFW or iptables allows traffic on port 80.

DNS not propagated. If you just set up your DNS record, it may take some time to propagate. Certbot will fail if the domain does not resolve to your server. Use dig to verify:

dig +short packetlog.org

Wrong Nginx server block. Certbot needs to find a server_name directive matching your domain. If you have multiple server blocks, make sure the right one is active.

Rate limits. Let’s Encrypt has rate limits: 50 certificates per registered domain per week. For normal use this is not an issue, but if you are testing repeatedly, you might hit it. Use the staging environment for testing:

certbot --nginx -d packetlog.org --staging

Staging certificates are not trusted by browsers, but the process is identical and the rate limits are much higher.

Security considerations

A few things worth doing after the basic setup:

Redirect all HTTP to HTTPS. Certbot does this by default, but verify it. There should be no way to access your site over plain HTTP.

Check your TLS configuration. The defaults from Certbot’s options-ssl-nginx.conf are generally good. They disable old protocols (TLS 1.0, 1.1) and weak ciphers.

Set up HSTS if you are committed to HTTPS. This tells browsers to always use HTTPS for your domain:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;

Start with a shorter max-age while testing, then increase it once you are confident everything works.

Further reading