All Deployment guides Deployment

How to Install a Custom SSL Certificate on Traefik (v3)

Traefik is famous for issuing certificates automatically via ACME. But sometimes you need to bring your own certificate instead — one issued by GetHTTPS, a corporate/internal CA, or a wildcard you manage elsewhere. This guide shows you exactly how, on Traefik v3.

The key thing to understand up front: you cannot attach a certificate file with a Docker label. Traefik labels control routing and enable TLS, but the certificate itself must come from the file provider as dynamic configuration. This trips up almost everyone the first time. We’ll do it the right way.

If you don’t have a certificate yet, get one free in 5 minutes.

When to use this (vs. Traefik’s automatic ACME)

Use automatic ACME (Let’s Encrypt)Bring your own certificate (this guide)
Public website, internet-reachableInternal/intranet service (no public DNS)
Standard DV certificate is fineYou need a specific CA, wildcard, or OV/EV cert
Traefik can complete HTTP/DNS challengeAir-gapped or firewalled host
You want zero certificate managementYou already have a cert from GetHTTPS or elsewhere

If automatic ACME fits your case, Traefik’s certificatesResolvers is simpler. This guide is for the “I already have fullchain.pem + privkey.pem and need Traefik to serve them” case.

Prerequisites

  • Traefik v3 running as a Docker container (the config below uses Docker, but the dynamic-config part applies to any setup)
  • Your certificate files — two of them:
    • fullchain.pem — your certificate + intermediate chain
    • privkey.pem — your private key
  • A domain pointing at the host (or a local hosts-file entry for internal use)

Which files does Traefik need? Traefik wants the full chain in certFile and the key in keyFile. If you use a leaf-only cert.pem, clients get “incomplete chain” errors — Traefik does not auto-append intermediates for file-provider certs. Use fullchain.pem. Certificate formats explained →

How Traefik’s config is split

Traefik has two kinds of configuration, and certificates live in the second one:

  • Static configuration — set at startup (entrypoints, providers). Changing it requires a restart. This goes in traefik.yml or container command flags.
  • Dynamic configuration — reloaded live without restart (routers, services, TLS certificates). This is what the file provider reads.

Your certificate is dynamic config. That’s why a label can’t carry it — labels are read from the Docker provider, but tls.certificates is a file-provider concept.

Step 1: Place the certificate files

Put the files in a directory you’ll mount into the container:

mkdir -p ./traefik/certs
cp fullchain.pem ./traefik/certs/
cp privkey.pem   ./traefik/certs/

# Lock down the private key
chmod 600 ./traefik/certs/privkey.pem
chmod 644 ./traefik/certs/fullchain.pem

Step 2: Write the dynamic configuration

Create ./traefik/dynamic/certs.yml. This is where you declare your certificate:

# ./traefik/dynamic/certs.yml  (dynamic configuration — file provider)
tls:
  certificates:
    - certFile: /certs/fullchain.pem
      keyFile: /certs/privkey.pem

  # Optional: make this cert the default for any host that doesn't
  # match a more specific certificate (e.g. a wildcard or internal CA).
  stores:
    default:
      defaultCertificate:
        certFile: /certs/fullchain.pem
        keyFile: /certs/privkey.pem

The paths (/certs/...) are paths inside the container — we mount them in the next step. Traefik selects the right certificate per request using SNI, matching the certificate’s SAN against the requested hostname. The defaultCertificate is the fallback when nothing else matches.

Step 3: Configure Traefik (static config + mounts)

Here’s a complete docker-compose.yml. The entrypoints and file provider are static config (passed as command flags); the certificate is loaded from the dynamic file you just wrote.

services:
  traefik:
    image: traefik:v3.3
    container_name: traefik
    restart: unless-stopped
    command:
      # ─── Entrypoints ───────────────────────────────
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      # Redirect all HTTP → HTTPS at the entrypoint level
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true"
      # ─── Providers ─────────────────────────────────
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      # File provider watches the dynamic config directory
      - "--providers.file.directory=/dynamic"
      - "--providers.file.watch=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./traefik/certs:/certs:ro"
      - "./traefik/dynamic:/dynamic:ro"

  # ─── Example backend service ──────────────────────
  whoami:
    image: traefik/whoami
    container_name: whoami
    labels:
      - "traefik.enable=true"
      # Router: match the host, serve on the HTTPS entrypoint
      - "traefik.http.routers.whoami.rule=Host(`example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      # Enable TLS on this router — certificate comes from the
      # dynamic config, NOT from a label.
      - "traefik.http.routers.whoami.tls=true"

The critical line is traefik.http.routers.whoami.tls=true. It tells Traefik “serve this router over TLS.” Traefik then picks the matching certificate from the file-provider store by SNI. There is no label like tls.certfile — that doesn’t exist for routers.

Step 4: Start and verify

docker compose up -d
docker compose logs -f traefik

Watch for the file provider loading your certificate. You should not see TLS errors. Then verify:

Browser check

Visit https://example.com. Click the padlock:

  • Issued by — your CA (Let’s Encrypt for GetHTTPS, or your internal CA)
  • Valid — start and end dates
  • Domain — matches the URL

Command-line check

# Confirm Traefik is serving YOUR certificate, not its self-signed default
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

If you see CN=TRAEFIK DEFAULT CERT, Traefik fell back to its built-in placeholder — your cert wasn’t loaded. See the Troubleshooting section below.

Step 5 (optional): Harden TLS

To control protocol versions and ciphers, add a TLS options block to your dynamic config and reference it from the router. Add to certs.yml:

tls:
  options:
    modern:
      minVersion: VersionTLS12
      sniStrict: true

Then on the router label:

- "traefik.http.routers.whoami.tls.options=modern@file"

The @file suffix tells Traefik this option set comes from the file provider. This restricts you to TLS 1.2 and 1.3 and rejects connections with no matching SNI.

HSTS: Add the Strict-Transport-Security header via a Traefik headers middleware once you’re confident HTTPS works everywhere. HSTS guide →

How to renew

Traefik does not auto-renew certificates you provide yourself — that’s the trade-off for bringing your own. When your cert nears expiry (day 60 of 90 for Let’s Encrypt):

  1. Get a new certificate from GetHTTPS (or your renewal tool).
  2. Overwrite the files in place:
    cp new-fullchain.pem ./traefik/certs/fullchain.pem
    cp new-privkey.pem   ./traefik/certs/privkey.pem
  3. Nothing else. Because --providers.file.watch=true is set, Traefik detects the file change and hot-reloads the certificate — no restart, no downtime. This is one of Traefik’s nicest properties for custom certs.

Full renewal guide →

Troubleshooting

Traefik serves “TRAEFIK DEFAULT CERT” instead of mine

Cause: Traefik couldn’t load your certificate, so it fell back to its self-signed placeholder. Almost always a path or mount problem.

Fix:

# 1. Confirm the files exist INSIDE the container at the expected path
docker compose exec traefik ls -l /certs

# 2. Confirm the dynamic file is mounted and readable
docker compose exec traefik cat /dynamic/certs.yml

# 3. Check logs for the real reason
docker compose logs traefik | grep -i "certificate\|tls\|error"

The certFile/keyFile paths in certs.yml must match the container paths (/certs/...), not your host paths.

”unable to find certificate for domain” / wrong certificate served

Cause: No certificate’s SAN matches the requested hostname, and no defaultCertificate is set.

Fix: Either add the hostname to your certificate, or set a defaultCertificate in the stores.default block (Step 2) so Traefik has a fallback.

Router returns 404, certificate never even checked

Cause: The router rule doesn’t match, or the service isn’t exposed. TLS is irrelevant until routing matches.

Fix:

# Verify the router exists and is enabled
docker compose exec traefik wget -qO- http://localhost:8080/api/http/routers 2>/dev/null | grep whoami

Make sure traefik.enable=true is on the backend container and the Host() rule matches the domain you’re testing.

Changes to certs.yml don’t take effect

Cause: The file provider isn’t watching, or you edited static config (which needs a restart).

Fix: Confirm --providers.file.watch=true is in the static config. Remember: entrypoints/providers are static (restart required); routers and tls.certificates are dynamic (hot-reloaded).

“x509: certificate signed by unknown authority” from clients

Cause: Incomplete chain — you used a leaf-only cert.pem in certFile instead of fullchain.pem.

Fix: Point certFile at the full chain. If you only have separate files, concatenate them (leaf first, then intermediates):

cat cert.pem intermediate.pem > fullchain.pem

Frequently asked questions

Can I specify the certificate file with a Docker label?

No. This is the #1 misconception. Labels set traefik.http.routers.<name>.tls=true to enable TLS, but the certificate files must be declared in dynamic configuration via the file provider (tls.certificates). There is no router label that points at a .pem file.

Do I need tls=true on the router if I have a certificate?

Yes. Declaring the certificate in tls.certificates makes it available, but each router still needs tls=true (or to listen on an entrypoint with TLS enabled) to actually serve over HTTPS. Defining a cert alone doesn’t switch a router to TLS.

Can I mix automatic ACME and custom certificates?

Yes. Traefik can use a certificatesResolver for some routers and file-provider certificates for others. Per request, an explicit tls.certificates match by SNI takes precedence over ACME for that hostname.

Where do certificates go on Kubernetes?

Not the file provider — on Kubernetes you provide certificates as TLS Secrets and reference them from the IngressRoute/Ingress. The file-provider approach in this guide is for Docker and standalone (non-K8s) setups.

Does Traefik auto-renew my custom certificate?

No. Auto-renewal only applies to certificates Traefik issues itself via ACME. For your own certs, you replace the files — but thanks to --providers.file.watch=true, Traefik hot-reloads them with zero downtime, as described in the How to renew section above.

Does this work with ECDSA/ECC certificates?

Yes. Traefik serves ECDSA certificates exactly like RSA — same certFile/keyFile config. GetHTTPS issues ECDSA P-256 by default for smaller, faster handshakes.

Related articles

Deployment 2026-05-08
SSL Certificates with Docker and Reverse Proxies
Configure HTTPS for Docker containers using Nginx reverse proxy, Traefik with automatic Let's Encrypt, or manual certificate mounting.
Getting Started 2026-05-08
How to Get a Free SSL Certificate (Step-by-Step Guide)
Get a free SSL certificate from Let's Encrypt in 5 minutes — no software to install, no account to create. Complete guide covering 4 methods, both challenge types, installation on 6 platforms, and troubleshooting.
Deployment 2026-05-08
How to Install an SSL Certificate on Nginx
Step-by-step guide to installing an SSL certificate on Nginx. Covers file upload, full server block config, TLS best practices, HTTP/2, HSTS, redirect setup, testing, and troubleshooting 6 common errors.
SSL & Certificates 2026-05-07
SSL Certificate Formats: PEM, PFX, DER Explained
Understand PEM, PFX/PKCS#12, and DER certificate formats. Learn which format your server needs and how to convert between them with OpenSSL.
Get a free SSL certificate in your browser
No installation, no account. Your private key never leaves your device.
Get your certificate