Skip to content 99% OFF 🎉 Anniversary Sale 99% OFF Shared Hosting Use Code HURRYUP Claim Offer 99% OFF Hosting
99% OFF Hosting — Code HURRYUP
Products
AI Website Builder New VPS Hosting Cloud Servers Web Hosting cPanel Hosting Dedicated Servers Domains
Company
About Documentation Support Center Contact Get Started Call +91 75795 45488
Login
Hosting Panel — cPanel & Billing Console Panel — VPS Management
ALL SYSTEMS OPERATIONAL
VPS

Traefik Reverse Proxy on a VPS: Docker Labels + Auto HTTPS

Getwebup 5 min read

If you're running more than one app in Docker on the same VPS, you've probably hit this: every new container needs its own Nginx vhost, its own Certbot cron job, and a reload every time something changes. Traefik gets rid of that busywork. It watches Docker directly, picks up new containers from their labels, and issues Let's Encrypt certificates automatically - no config file to touch when you deploy app number six.

Why Traefik Instead of Nginx or Caddy

Nginx and Caddy are both solid reverse proxies, but they're static by design - you edit a config file, then reload the service. Traefik is built around service discovery: it talks to the Docker API, sees which containers are running, and builds its routing table from labels on those containers. Add a container with the right labels and Traefik picks it up within seconds. Stop it, and the route disappears. No reload, no stale vhost files.

FeatureNginxCaddyTraefik
Config sourceStatic filesStatic CaddyfileDocker labels (dynamic)
Auto HTTPSManual + CertbotBuilt inBuilt in
Reload on new appRequiredRequiredNot required
Best fitSingle app / fine controlSimple static sitesMultiple Docker containers

If you're only ever running one WordPress site on a VPS, Nginx or Caddy is honestly simpler. Traefik earns its keep once you're juggling several containerized apps - staging environments, internal tools, client projects - on one box.

What You'll Need

  • A VPS with Docker and Docker Compose v2 installed
  • A domain (or subdomain) with an A record pointing at the VPS's public IP
  • Ports 80 and 443 open in your firewall (ufw allow 80/tcp && ufw allow 443/tcp)
  • Root or sudo SSH access

Step 1: Create the Traefik Docker Compose File

Make a directory for your reverse proxy stack, separate from your application containers:

mkdir -p ~/traefik/letsencrypt
cd ~/traefik
touch acme.json
chmod 600 acme.json

That acme.json file is where Traefik stores your issued certificates and private keys. The 600 permission isn't optional - Traefik will refuse to start if it's world-readable.

Now create docker-compose.yml:

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=you@yourdomain.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    networks:
      - proxy

networks:
  proxy:
    name: proxy
    external: false

Two lines matter more than the rest: providers.docker.exposedbydefault=false means Traefik ignores every container unless it's explicitly labeled, and the tlschallenge resolver handles the ACME/Let's Encrypt handshake without you running Certbot separately.

Step 2: Start Traefik

docker compose up -d
docker compose logs -f traefik

You should see Traefik start and bind to ports 80 and 443. If it exits immediately, it's almost always the acme.json permissions or a port already in use by Apache/Nginx from a previous setup - check with ss -tlnp | grep -E ':80|:443'.

Step 3: Add Your First App with Labels

Here's a sample app - say a Node app or a WordPress container - joining the same proxy network with routing labels instead of a vhost file:

services:
  myapp:
    image: myapp:latest
    container_name: myapp
    restart: unless-stopped
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls.certresolver=myresolver"
      - "traefik.http.services.myapp.loadbalancer.server.port=3000"

networks:
  proxy:
    external: true

The loadbalancer.server.port label tells Traefik which port the container listens on internally - it does not need to be published with ports: at all, since Traefik reaches it over the shared Docker network.

docker compose up -d

Within a few seconds, hit https://app.yourdomain.com. Traefik should have already requested and installed a certificate - check docker compose -f ~/traefik/docker-compose.yml logs traefik for a line confirming the ACME certificate was obtained.

Common Problems and Fixes

"404 page not found" from Traefik itself

This means Traefik is running but no router matched your request. Usually it's a mismatched Host() rule, a missing traefik.enable=true label, or the app container isn't on the proxy network. Run docker network inspect proxy and confirm both Traefik and your app show up.

Certificate never issues

Check three things in order: DNS actually resolves to this VPS (dig +short app.yourdomain.com), port 80 is reachable from the outside (not blocked by a cloud firewall in front of UFW), and you haven't hit Let's Encrypt's rate limit from repeated failed attempts. If you've been testing a lot, switch temporarily to the staging CA (--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory) so failures don't burn your production rate limit.

Dashboard exposed to the internet

If you enabled --api.insecure=true to peek at the dashboard while testing, remove it before going live - that flag serves the dashboard on port 8080 with no authentication. Either drop the flag entirely or put the dashboard behind its own router with basic auth middleware.

Containers on different networks can't be routed

Every app Traefik should route to needs to join the same external network you created for the proxy. A container on the default Compose network is invisible to Traefik even with correct labels.

Prevention: Keep It Stable Long-Term

  • Pin the Traefik image to a specific version (traefik:v3.1, not :latest) so an upstream update doesn't change your routing behavior overnight.
  • Back up acme.json off the server - losing it means re-issuing every certificate and risking rate limits.
  • Keep exposedbydefault=false so a stray container never gets accidentally routed to the public internet.
  • Restrict the Docker socket mount to read-only (:ro), as shown above - Traefik only needs to read container metadata, never control containers.

Once this is set up, adding a new app to your VPS is a docker compose file and two labels - no touching the reverse proxy at all. If you'd rather not manage any of this yourself, Getwebup's managed VPS plans include Docker and reverse proxy setup as part of onboarding.

Frequently asked questions

Do I need Nginx or Apache installed alongside Traefik?

No. Traefik replaces them as the entry point for HTTP/HTTPS traffic on ports 80 and 443. If Nginx or Apache is already bound to those ports from a previous setup, stop and disable that service first, or Traefik won't be able to start.

Can Traefik route to a non-Docker app, like a WordPress site running directly on the VPS via cPanel?

Yes, using the file provider instead of the Docker provider - you'd define a static router pointing to 127.0.0.1:<port>. It's more manual setup than label-based Docker routing, so if most of your sites are traditional LAMP/cPanel hosting, Nginx or Caddy is usually simpler.

What happens to my certificates if I rebuild the Traefik container?

Nothing, as long as the ./letsencrypt folder (containing acme.json) is mounted as a volume like in the compose file above. Certificates persist across container restarts and rebuilds - they're only lost if you delete that folder.

Is the TLS challenge method reliable behind Cloudflare's proxy?

It can be flaky, because Cloudflare's proxy terminates TLS before it reaches your VPS. If your domain is proxied (orange cloud) through Cloudflare, switch to the DNS-01 challenge with Traefik's Cloudflare plugin instead of tlschallenge, or set the DNS record to 'DNS only' while certificates are being issued.

#traefik #docker #reverse-proxy #vps #lets-encrypt #docker-compose

Keep reading

Chat with Support