Skip to content
Serverküche
Search

Loading search … (only available on the published site).

Basics Difficulty: Beginner

How HTTPS actually works (and what Traefik does for you)

Certificate, chain of trust, Let's Encrypt, handshake: this foundational guide explains HTTPS with real commands – and what Traefik automates.

· 8 min read ·Duration: approx. 20 minutes
Table of contents

The padlock icon in your browser is the most used and least understood security feature of the web. In this concept guide you take a look behind it – with real commands instead of dry theory. Afterwards you’ll read certificate errors like a list of ingredients.

What are we building?

No new service this time, but understanding: by the end you know what a TLS certificate is, why your browser trusts it, how Let’s Encrypt proves for free that a domain belongs to you, and what happens during the TLS handshake. You verify every building block yourself with openssl and curl – the examples were run with OpenSSL 3.5 on Debian 13 against the real serverkueche.de and work against any HTTPS website. This is exactly the knowledge Traefik turns into automation for you – and when something jams there, you’ll know where to look.

Prerequisites

  • An understanding of how a domain points to your server (A record, dig)
  • Any Linux terminal – your server or your laptop; you only need standard tools (openssl, curl, dig from the dnsutils package)
  • No running web server required – we inspect existing websites
🍳 Recommendation Ad

VPS 1000 G12

4 vCores · 8 GB RAM · 256 GB NVMe

from €10.36/month

Any machine will do for following along – a small VPS for your own HTTPS setup later.

Go to netcup →

💶 5 € voucher for new netcup customers:36nc17844976032 (new customers only, no domains)

Step by step

Step 1: The two problems TLS solves

HTTPS is plain HTTP, transported through a TLS tunnel (Transport Layer Security). The tunnel solves two problems at once:

  1. Confidentiality: Nobody between browser and server can read along or tamper unnoticed – not the café Wi-Fi, not your ISP.
  2. Authenticity: You are really talking to the server behind YOUR_DOMAIN – not to an attacker who slipped in between.

Point 2 requires an ID card: the certificate.

The padlock does not mean “harmless”

The padlock only confirms an encrypted connection to the domain shown. A phishing site gets a valid certificate just as easily – TLS certifies the pipe, not the content.

Step 2: Inspecting a real certificate

openssl s_client opens a TLS connection and shows what the server presents. Replace YOUR_DOMAIN with any HTTPS domain – here the command runs against serverkueche.de:

Terminal
echo | openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN 2>/dev/null | openssl x509 -noout -subject -issuer -dates

The echo | closes the connection immediately, the second openssl call translates the presented certificate into plain text:

Ausgabe
subject=CN=serverkueche.de
issuer=C=US, O=Let's Encrypt, CN=YR1
notBefore=Jul 16 11:33:26 2026 GMT
notAfter=Oct 14 11:33:25 2026 GMT

These fields are worth being able to read:

  • subject – who the ID card is for. Which hostnames exactly are covered is listed in the Subject Alternative Name field (here: serverkueche.de and www.serverkueche.de – a subdomain like blog.serverkueche.de would not be covered and would need its own certificate).
  • issuer – who issued it: the certificate authority (CA), here Let’s Encrypt with its intermediate certificate YR1.
  • notBefore/notAfter – the validity. Do the math: July 16 to October 14 is 90 days, the standard lifetime of Let’s Encrypt certificates. That’s why automatic renewal is not a convenience feature but mandatory.

Step 3: The chain of trust – why your browser believes the ID card

How does your browser know YR1 isn’t a made-up name? Through a chain. Show all certificates of the connection:

Terminal
echo | openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN 2>/dev/null | grep -E "^ *[0-9] s:|^ *i:"
Ausgabe
 0 s:CN=serverkueche.de
   i:C=US, O=Let's Encrypt, CN=YR1
 1 s:C=US, O=Let's Encrypt, CN=YR1
   i:C=US, O=ISRG, CN=Root YR
 2 s:C=US, O=ISRG, CN=Root YR
   i:C=US, O=Internet Security Research Group, CN=ISRG Root X1

Read it top to bottom (s: = subject, i: = issued by): the domain certificate (0) was signed by the intermediate YR1 (1), which in turn was signed by a root certificate of ISRG, the organisation behind Let’s Encrypt (2). Root certificates are the trust anchor: they ship embedded in your operating system and browser (the “root store”, on Debian the ca-certificates package). If the browser can build an unbroken chain of signatures from the server certificate to a root in its store, the connection counts as trustworthy – openssl reports that in the s_client output as:

Ausgabe
Verify return code: 0 (ok)

Anything other than 0 (ok) is one of the failure cases in “When things go wrong”.

Step 4: How Let’s Encrypt verifies the domain is yours

A CA may only issue a certificate if you prove control over the domain. This runs through the ACME protocol, fully automated, in two flavours:

  • HTTP-01: The CA says “place file X at http://YOUR_DOMAIN/.well-known/acme-challenge/X”. Only whoever controls the server behind the A record can do that. That’s why port 80 must be reachable from the internet – and why the dig check in the domain tutorial comes before the first certificate: if the A record points nowhere, the CA never finds the file.
  • DNS-01: Instead of a file you set a TXT record _acme-challenge.YOUR_DOMAIN. This needs API access to your DNS zone, but works without an open port – and is the only way to get wildcard certificates (*.YOUR_DOMAIN).

Once your server passes the challenge, Let’s Encrypt signs your certificate with YR1 – and the chain from step 3 is complete.

Step 5: The handshake – what happens on every connection

The certificate proves identity; encryption uses something else. During the TLS handshake, browser and server agree on a shared session key within a few milliseconds. curl shows you the result:

Terminal
curl -sIv https://YOUR_DOMAIN/ 2>&1 | grep -E "SSL connection|HTTP/"
Ausgabe
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* using HTTP/2
HTTP/2 200

That one line summarises the whole handshake: protocol TLS 1.3 (the current standard), key exchange over the x25519 curve – this is where the session key is created without ever crossing the wire –, then AES-128-GCM encrypts the actual HTTP traffic, and RSASSA-PSS is the server’s signature made with the private key belonging to its certificate. That private key is the real secret: whoever holds it can impersonate your domain.

Step 6: What Traefik takes off your plate

Now you can put into context everything Traefik handles in the background:

  • ACME client: ordering the certificate, answering the HTTP-01 challenge on port 80, fetching the chain – automatically the first time a new app starts.
  • Storage: certificates including private keys end up in acme.json – which is why Traefik insists on strict file permissions there (chmod 600).
  • Renewal: around 30 days before expiry, Traefik fetches a fresh certificate without you lifting a finger – you never notice the 90-day lifetime.
  • Serving: per request, Traefik picks the matching certificate based on the requested hostname (SNI) and redirects HTTP to HTTPS.

Without this automation, HTTPS would be monthly manual labour – expired certificates are one of the most common self-inflicted outages there is.

When things go wrong

Browser shows NET::ERR_CERT_AUTHORITY_INVALID, curl says self-signed certificate. The server is serving Traefik’s built-in fallback certificate (TRAEFIK DEFAULT CERT) because issuance failed. Almost always: the A/AAAA record points to the wrong IP (check with dig YOUR_DOMAIN) or port 80 is closed in the firewall – the HTTP-01 challenge never arrives. The exact cause is in the Traefik logs: docker compose logs traefik | grep -i acme.

The issuer says (STAGING) Ersatz Emmer YR2. Let’s Encrypt’s test CA is still active – browsers distrust it on purpose. Remove the staging line from the Traefik configuration, empty acme.json, restart the container (details in the Traefik tutorial).

certificate has expired. Automatic renewal has been failing for at least 30 days – same list of causes as the first error, just long unnoticed. If the message appears on one device only: check that device’s system clock, TLS is time-sensitive.

Let’s Encrypt reports too many certificates already issued. Rate limit hit, usually from trial-and-error loops against the production CA. Switch to the staging CA until your setup works – and back up acme.json instead of re-issuing certificates.

unable to get local issuer certificate on an old client. The system is missing the root certificate from step 3 – its root store is outdated. On Debian: apt update && apt install ca-certificates.

Maintenance & backups

  • With automated issuance: nothing monthly. That is precisely the point of the Traefik automation. What you do instead is monitor expiry – Uptime Kuma checks the certificate as part of HTTPS monitors and warns well before the expiry date. That way a renewal that has been failing for weeks gets noticed before visitors see error pages.
  • acme.json belongs in your backup – it contains private keys, so back it up encrypted only, e.g. with Restic. After rebuilding a server, all certificates would otherwise have to be re-issued, which can hit the rate limit.
  • Lifetimes are shrinking industry-wide: the CA/Browser bodies have decided to reduce the maximum certificate lifetime step by step – from 200 days (2026) via 100 days (2027) down to 47 days from 2029. Let’s Encrypt’s 90 days aren’t stingy, they’re the future for everyone. If you automate today, you won’t notice any of it.

You might also like