---
title: "Traefik Reverse Proxy in Docker: Complete Setup Guide"
description: "Set up Traefik as a Docker reverse proxy with automatic Let's Encrypt TLS certificates. Step-by-step guide covering v3.7, dashboard security, and app deployment."
date: 2026-08-02
categories: ["self-hosting"]
tags: ["traefik","docker","reverse-proxy"]
---

import YouTubeEmbed from "../../components/widgets/YouTubeEmbed.astro";
import Button from "../../components/widgets/Button.astro";
import Notice from "../../components/widgets/Notice.astro";
import ListCheck from "../../components/widgets/ListCheck.astro";
import Accordion from "../../components/widgets/Accordion.astro";
import Tabs from "../../components/widgets/Tabs.astro";
import Tab from "../../components/widgets/Tab.astro";
import { Picture } from "astro:assets";
import img1 from "../../assets/images/24/07/traefik-diagram.jpeg";

If you're running Docker on a VPS and need a reverse proxy that handles TLS certificates automatically, Traefik is the best option. I've been using Cloudflare Tunnels for most of my setups, but Traefik gives you full control. No third-party tunnel dependency, automatic Let's Encrypt certificates, and native Docker integration. In this guide, we'll set up Traefik as a reverse proxy in Docker from scratch: VPS creation, Docker install, Traefik v3.7 configuration, dashboard security, and your first app behind the proxy.

<Notice type="error" title="Security Warning">
The original version of this guide used Traefik v3.1, which reached end of life on October 28, 2024 and has known critical vulnerabilities (CVE-2024-45410, CVSS 7.5 HIGH per NVD / 9.8 per GitHub's advisory). This guide has been updated to Traefik v3.7. If you are running v3.1, upgrade immediately.
</Notice>

<Notice type="info">
If you need a Let's Encrypt wildcard certificate with Cloudflare DNS challenge, see: <a href="https://www.bitdoze.com/traefik-wildcard-certificate/">Traefik FREE Let's Encrypt Wildcard Certificate With Cloudflare Provider</a>
</Notice>

## What is Traefik?

[Traefik](https://traefik.io/traefik/) is a modern reverse proxy and load balancer designed for containerized environments. It routes traffic to your microservices and applications by automatically discovering and configuring routes based on your infrastructure.

Traefik's Docker integration is what sets it apart. It detects new containers and updates its routing config automatically. You don't have to touch a config file every time you add a service.

Main features:

1. Automatic service discovery and configuration
2. Support for multiple protocols (HTTP, HTTPS, TCP, UDP)
3. Built-in monitoring dashboard
4. Built-in Let's Encrypt integration for automatic SSL/TLS certificate management
5. Support for various load balancing algorithms
6. Middleware for adding extra functionality like authentication or rate limiting

<Picture src={img1} alt="Traefik architecture diagram showing EntryPoints, Routers, and Middlewares flow" />

## Traefik's architecture

Traefik's architecture is built around three main components: EntryPoints, Routers, and Middlewares.

### EntryPoints

EntryPoints are the network entry points into Traefik. They define the ports and protocols on which Traefik listens for incoming traffic.

What EntryPoints do:

- Define listening ports for HTTP, HTTPS, or UDP traffic
- Can be configured for TCP and UDP protocols
- Support for multiple EntryPoints (e.g., separate ones for HTTP and HTTPS)
- Can be associated with specific IP addresses

### Routers

Routers are responsible for connecting incoming requests to the services that can handle them. They analyze the requests using rules and route them accordingly.

What Routers do:

- Use rules to determine which requests they should handle
- Can be associated with specific EntryPoints
- Support priority settings to manage overlapping rules
- Can be configured for HTTP, TCP, or UDP traffic

### Middlewares

Middlewares tweak the requests before they are sent to your service (or the responses before they are sent back to the clients). They can be attached to routers and provide a way to apply modifications to requests or responses.

What Middlewares do:

- Can modify requests and responses
- Chainable (multiple middlewares can be applied in sequence)
- Provide functionality such as authentication, rate limiting, headers manipulation, etc.
- Can be reused across multiple routers

Traefik has three core components: EntryPoints (where it listens), Routers (how it handles requests), and Middlewares (request/response modifications). Together they form a flexible routing system for containerized environments.

## How to setup Traefik as a reverse proxy for your Docker apps

<YouTubeEmbed
  url="https://www.youtube.com/embed/vce3EEkvuZ4"
  label="How to Use Traefik as A Reverse Proxy in Docker"
/>

<Notice type="info">
Want to monitor server resources like CPU, memory, and disk space? See: <a href="https://www.bitdoze.com/sever-monitoring/">How To Monitor Server and Docker Resources</a>
</Notice>

After we have seen what Traefik is, we are going to go through all the steps needed: create and configure a VPS, install Docker, configure DNS, set up Traefik with the dashboard, and deploy some applications.

### 1. Create a VPS server

You need a VPS with ports 22, 80, and 443 open. I use [Hetzner](https://go.bitdoze.com/hetzner) or [Hostinger](https://go.bitdoze.com/hostinger-vps) for most setups. Use your provider's cloud firewall if available (e.g., Hetzner Cloud Firewall). It's more reliable than UFW alone because of how Docker interacts with iptables (more on that in Step 4).

<Notice type="info">
For hardening your VPS beyond basic firewall rules, consider <a href="https://www.bitdoze.com/crowdsec-secure-server/">securing your VPS with CrowdSec</a>. It works well alongside Traefik.
</Notice>

### 2. Add SWAP

Most VPS servers don't have swap by default. Add it with:

```sh
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```

### 3. Install Docker

The next step is installing Docker and Docker Compose v2. The commands below auto-detect your distro codename (works for both Ubuntu and Debian):

```sh
# Add Docker's official GPG key
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository (auto-detects Ubuntu/Debian codename)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Verify the install worked:

```sh
docker --version
docker compose version
```

You should see version output for both. If `docker compose version` errors, the Compose plugin wasn't installed correctly.

### 4. Configure firewall and update OS

Update the OS first:

```sh
sudo apt update && sudo apt upgrade -y
```

If you're using UFW, open the required ports:

```sh
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status
```

<Notice type="warning" title="Docker bypasses UFW">
Docker manipulates iptables directly, which can bypass UFW rules. Containers with published ports may be accessible even if UFW blocks that port. Consider using your cloud provider's firewall (e.g., Hetzner Cloud Firewall) or the DOCKER-USER iptables chain. See: <a href="https://www.bitdoze.com/docker-bypasses-firewall/">Docker Bypasses UFW Firewall Rules</a>
</Notice>

Reboot after updates:

```sh
reboot
```

### 5. Create the Docker network

Create the external network that Traefik and your apps will share:

```sh
docker network create traefik-net
```

Verify:

```sh
docker network ls | grep traefik-net
```

You should see `traefik-net` listed with bridge driver.

### 6. Create the Traefik reverse proxy Docker Compose file

Create the directory and navigate to it:

```sh
mkdir -p /opt/stacks/traefik && cd /opt/stacks/traefik
```

<Notice type="info">
This guide uses the TLS-ALPN-01 challenge, which requires port 443 to be reachable from the internet. If you need wildcard certificates or can't open port 443, use DNS challenge instead. See: <a href="https://www.bitdoze.com/traefik-wildcard-certificate/">Traefik Let's Encrypt Wildcard Certificate</a>
</Notice>

The recommended setup uses a Docker socket proxy to limit Traefik's access to the Docker API. If you want the simpler direct-socket version, use the second tab below.

<Tabs>
<Tab name="Recommended: Socket Proxy">

Create a `compose.yml` file:

```yaml
services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy:latest
    container_name: socket-proxy
    restart: unless-stopped
    networks:
      - traefik-net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONTAINERS: 1
      SERVICES: 1
      TASKS: 1
      NETWORKS: 1
    security_opt:
      - no-new-privileges:true

  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    command:
      #- --log.level=DEBUG
      - --api.dashboard=true
      - --ping=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.endpoint=tcp://socket-proxy:2375
      - --providers.docker.network=traefik-net
      - --entrypoints.http.address=:80
      - --entrypoints.http.http.redirections.entrypoint.to=https
      - --entrypoints.http.http.redirections.entrypoint.scheme=https
      - --entrypoints.https.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      #- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.letsencrypt.acme.email=you@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-net
    ports:
      - 80:80
      - 443:443
    healthcheck:
      test: ["CMD", "traefik", "healthcheck", "--ping"]
      interval: 30s
      timeout: 5s
      retries: 3
    env_file: .env
    volumes:
      - ./letsencrypt:/letsencrypt
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.com`)
      - traefik.http.routers.traefik-secure.entrypoints=https
      - traefik.http.routers.traefik-secure.service=api@internal
      - traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt
      - traefik.http.routers.traefik-secure.middlewares=traefik-auth
      - traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}
      - traefik.http.routers.traefik-secure.tls=true

networks:
  traefik-net:
    external: true
```

</Tab>
<Tab name="Simple: Direct Socket">

If you prefer the simpler setup without a socket proxy, create a `compose.yml` file:

```yaml
services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    command:
      #- --log.level=DEBUG
      - --api.dashboard=true
      - --ping=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=traefik-net
      - --entrypoints.http.address=:80
      - --entrypoints.http.http.redirections.entrypoint.to=https
      - --entrypoints.http.http.redirections.entrypoint.scheme=https
      - --entrypoints.https.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      #- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.letsencrypt.acme.email=you@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-net
    ports:
      - 80:80
      - 443:443
    healthcheck:
      test: ["CMD", "traefik", "healthcheck", "--ping"]
      interval: 30s
      timeout: 5s
      retries: 3
    env_file: .env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.com`)
      - traefik.http.routers.traefik-secure.entrypoints=https
      - traefik.http.routers.traefik-secure.service=api@internal
      - traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt
      - traefik.http.routers.traefik-secure.middlewares=traefik-auth
      - traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}
      - traefik.http.routers.traefik-secure.tls=true

networks:
  traefik-net:
    external: true
```

This mounts the Docker socket read-only directly into the Traefik container. It works fine for single-user setups, but the socket proxy version above limits blast radius if Traefik is compromised.

</Tab>
</Tabs>

**Command options explained:**

1. `--api.dashboard=true`: Enables the Traefik web dashboard.
2. `--providers.docker=true`: Enables Docker as a provider for automatic service discovery.
3. `--providers.docker.exposedbydefault=false`: Prevents Traefik from automatically exposing all containers. You must explicitly enable each one.
4. `--providers.docker.endpoint=tcp://socket-proxy:2375`: Connects to the Docker API through the socket proxy instead of the raw socket. (Omitted in the direct-socket version.)
5. `--providers.docker.network=traefik-net`: Tells Traefik which Docker network to use for routing traffic to containers.
6. `--entrypoints.http.address=:80`: HTTP entrypoint on port 80.
7. `--entrypoints.http.http.redirections.entrypoint.to=https`: Redirect all HTTP traffic to HTTPS.
8. `--entrypoints.http.http.redirections.entrypoint.scheme=https`: Ensures the redirect uses the HTTPS scheme. For a deeper dive on HTTP to HTTPS redirects, see [Traefik HTTP to HTTPS redirect](https://www.bitdoze.com/traefik-redirect-http-https/).
9. `--entrypoints.https.address=:443`: HTTPS entrypoint on port 443.
10. `--certificatesresolvers.letsencrypt.acme.tlschallenge=true`: Enables the TLS-ALPN-01 challenge for Let's Encrypt certificate acquisition.
11. `--certificatesresolvers.letsencrypt.acme.email=you@example.com`: Your email for Let's Encrypt registration and expiry notifications.
12. `--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json`: Where to store certificates. This file needs specific permissions (covered in Step 9).
13. `--ping=true`: Enables the healthcheck endpoint used by Docker healthcheck.

**Dashboard labels explained:**

1. `traefik.enable=true`: Enables Traefik for this container.
2. `traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.com`)`: Routes requests for this hostname to the dashboard.
3. `traefik.http.routers.traefik-secure.entrypoints=https`: Uses the HTTPS entrypoint.
4. `traefik.http.routers.traefik-secure.service=api@internal`: Routes to Traefik's internal dashboard API.
5. `traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt`: Uses Let's Encrypt for TLS certificates.
6. `traefik.http.routers.traefik-secure.middlewares=traefik-auth`: Applies the auth middleware.
7. `traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}`: Sets up Basic Auth. Credentials come from the `.env` file. More on [Traefik Basic Authentication](https://www.bitdoze.com/traefik-basic-authentication/).
8. `traefik.http.routers.traefik-secure.tls=true`: Enables TLS for this router.

Replace `traefik.yourdomain.com` with your actual domain.

### 7. Create the `.env` file for Traefik dashboard credentials

Install htpasswd:

```sh
sudo apt install apache2-utils -y
```

Generate a bcrypt hash and write it to `.env` with the variable name:

```sh
echo "TRAEFIK_DASHBOARD_CREDENTIALS=$(htpasswd -nbB admin 'YourPassword123' | sed 's/\$/$$/g')" | sudo tee -a .env
```

The `-B` flag uses bcrypt. The `sed 's/\$/$$/g'` doubles every `$`: Docker Compose interpolates `$VAR` inside `.env` files, so a single-`$` hash would get mangled (the letters after `$` are eaten as an undefined variable) and auth would silently fail with 401. With `$$` the container receives the correct single-`$` hash.

Verify the `.env` contains the doubled hash:

```sh
cat .env
# TRAEFIK_DASHBOARD_CREDENTIALS=admin:$$2y$$05$$...hash...
```

Replace with the actual output from the htpasswd command.

<Notice type="info">
For production, consider using <a href="https://www.bitdoze.com/docker-compose-secrets/">Docker Compose secrets</a> instead of .env files for sensitive credentials.
</Notice>

### 8. Point the domain to your server IP

Create an A record for your domain pointing to the server IP. For subdomains, you can either:

- Create individual A records for each subdomain (e.g., `traefik.yourdomain.com`, `flowise.yourdomain.com`)
- Create a wildcard A record (`*.yourdomain.com`) pointing to the server

Individual records are more explicit and easier to debug. If you're not using a wildcard, make sure the `traefik.yourdomain.com` record is created before proceeding.

Verify DNS propagation:

```sh
dig traefik.yourdomain.com +short
```

Should return your server IP. If it returns nothing, wait a few more minutes. The TLS challenge requires DNS to resolve correctly.

### 9. Start Traefik and verify Let's Encrypt TLS certificates

Before starting, create the `acme.json` file with correct permissions:

```sh
mkdir -p ./letsencrypt
touch ./letsencrypt/acme.json
chmod 600 ./letsencrypt/acme.json
```

<Notice type="warning" title="acme.json permissions">
If acme.json does not have 600 permissions, Traefik will refuse to start or fail to store certificates. This is the #1 beginner issue. Verify with `ls -la ./letsencrypt/acme.json`: the output should show `-rw-------`.
</Notice>

Start Traefik:

```sh
docker compose up -d
```

**Verify it works:**

1. Check container status: `docker ps`: the traefik container should show "healthy" (the healthcheck takes ~30 seconds).

2. Check logs for cert issuance:
```sh
docker logs traefik
```
Look for a line about certificate being obtained. No errors about `acme.json` or permissions.

3. Test HTTP→HTTPS redirect:
```sh
curl -I http://traefik.yourdomain.com
```
Expect a `301 Moved Permanently` redirecting to HTTPS.

4. Test TLS certificate:
```sh
curl -vI https://traefik.yourdomain.com 2>&1 | grep -i "issuer"
```
Should show `issuer: CN=R3, O=Let's Encrypt, C=US` (or similar). If you see a self-signed cert, DNS isn't propagated or port 443 isn't reachable.

5. Access the dashboard: Open `https://traefik.yourdomain.com` in your browser. You should get a Basic Auth prompt, then see the Traefik dashboard.

<Notice type="info">
During testing, uncomment the staging CA server line in your compose file to avoid hitting Let's Encrypt rate limits. Switch back to the production server once everything works. For more essential Docker debugging commands, see: <a href="https://www.bitdoze.com/docker-commands/">essential Docker commands</a>
</Notice>

**Let's Encrypt certificate note:** Certificates are valid for 90 days currently. Traefik auto-renews them (starting 30 days before expiry). Let's Encrypt is [transitioning to 45-day certificates by February 2028](https://letsencrypt.org/2025/12/02/from-90-to-45). Traefik's auto-renewal will handle this transparently, but `acme.json` must remain writable and the container must stay running for renewals to succeed.

### 10. Deploy your first app behind Traefik reverse proxy

Now that Traefik is running, we can add applications. Here's a FlowiseAI example with a PostgreSQL database. Previously I used [FlowiseAI with Docker Compose](https://www.bitdoze.com/flowiseai-install/) behind Cloudflare Tunnels. Now we're using Traefik labels instead:

```yaml
services:
  flowise-db:
    image: postgres:16-alpine
    hostname: flowise-db
    networks:
      - traefik-net
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./flowise-db-data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5

  flowise:
    image: flowiseai/flowise:latest
    container_name: flowiseai
    hostname: flowise
    healthcheck:
      test: wget --no-verbose --tries=1 --spider http://localhost:${PORT}
    volumes:
      - ./flowiseai:/root/.flowise
    environment:
      DEBUG: false
      PORT: ${PORT}
      FLOWISE_USERNAME: ${FLOWISE_USERNAME}
      FLOWISE_PASSWORD: ${FLOWISE_PASSWORD}
      APIKEY_PATH: /root/.flowise
      SECRETKEY_PATH: /root/.flowise
      LOG_LEVEL: info
      LOG_PATH: /root/.flowise/logs
      DATABASE_TYPE: postgres
      DATABASE_PORT: 5432
      DATABASE_HOST: flowise-db
      DATABASE_NAME: ${POSTGRES_DB}
      DATABASE_USER: ${POSTGRES_USER}
      DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
    restart: on-failure:5
    networks:
      - traefik-net
    depends_on:
      flowise-db:
        condition: service_healthy
    entrypoint: /bin/sh -c "sleep 3; flowise start"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.flowise.rule=Host(`flowise.domain.com`)"
      - "traefik.http.routers.flowise.entrypoints=https"
      - "traefik.http.routers.flowise.tls.certresolver=letsencrypt"
      - "traefik.http.services.flowise.loadbalancer.server.port=${PORT}"

networks:
  traefik-net:
    external: true
```

<Notice type="warning">
The `flowiseai/flowise:latest` tag always pulls the newest image. For stability, check the <a href="https://github.com/FlowiseAI/Flowise/releases">Flowise releases</a> and pin to a specific version instead.
</Notice>

A few things to note about this config:

Both `flowise-db` and `flowise` are on `traefik-net`. The DB and Flowise can also share a separate internal network for database traffic. They just both need `traefik-net` for Traefik to route external traffic.

The Traefik-specific labels tell Traefik to route traffic for `flowise.domain.com` to this container on the HTTPS entrypoint, using Let's Encrypt for TLS.

We don't publish any host ports. Traefik routes traffic through the Docker network internally. The `loadbalancer.server.port` label tells Traefik which port the app listens on inside the container.

Replace `flowise.domain.com` with your actual domain.

Create the `.env` file for Flowise:

```sh
PORT=3000
POSTGRES_USER='user'
POSTGRES_PASSWORD='pass'
POSTGRES_DB='flowise'
FLOWISE_USERNAME=bitdoze
FLOWISE_PASSWORD=bitdoze
```

If the subdomain isn't using a wildcard, make sure `flowise.domain.com` A record points to your server IP first. Then:

```sh
source .env
docker compose up -d
```

Verify: `docker logs flowiseai` and open `https://flowise.domain.com` in your browser.

For more Docker container ideas to deploy behind Traefik, check out [Docker containers for your home server](https://www.bitdoze.com/docker-containers-home-server/).

### 11. Install Dockge to manage your Docker Compose files

Dockge is a lightweight Docker Compose manager with a web UI. I've written a detailed [Dockge Docker Compose manager](https://www.bitdoze.com/dockge-install/) install guide. Here's how to run it behind Traefik.

Create a directory:

```sh
mkdir /opt/dockge
cd /opt/dockge
```

Create a `compose.yml` file:

```yaml
services:
  dockge:
    image: louislam/dockge:1
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/stacks:/opt/stacks
    environment:
      - DOCKGE_STACKS_DIR=/opt/stacks
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dockge.rule=Host(`dockge.domain.com`)"
      - "traefik.http.routers.dockge.entrypoints=https"
      - "traefik.http.routers.dockge.tls.certresolver=letsencrypt"
      - "traefik.http.services.dockge.loadbalancer.server.port=5001"

networks:
  traefik-net:
    external: true
```

The `louislam/dockge:1` tag follows the major version, which is the correct practice for stability. Dockge 1.5.0+ disables the built-in terminal/console by default for security. If you need terminal access, add `DOCKGE_ENABLE_CONSOLE=true` to the environment section.

If you're not using a wildcard for subdomains, make sure `dockge.domain.com` A record is pointing to your server IP. Then:

```sh
docker compose up -d
```

Verify: `docker logs dockge` and open `https://dockge.domain.com` in your browser.

For a broader comparison of self-hosted management tools, see [self-hosted server management panels](https://www.bitdoze.com/best-self-hosted-panels/).

## Troubleshooting common problems

<Accordion label="Traefik won't start / acme.json errors" group="troubleshooting">
The most common cause is wrong permissions on `acme.json`. Traefik requires `600` permissions on this file.

```sh
ls -la ./letsencrypt/acme.json
# Should show: -rw------- 1 root root ...
```

If permissions are wrong:

```sh
chmod 600 ./letsencrypt/acme.json
docker compose restart traefik
```

Also check `docker logs traefik` for other errors (port conflicts, network issues).
</Accordion>

<Accordion label="No certificate / self-signed cert warning" group="troubleshooting">
This means the TLS challenge failed. Common causes:

1. **DNS not propagated:** Verify with `dig traefik.yourdomain.com +short`: it must return your server IP.
2. **Port 443 not reachable:** The TLS-ALPN-01 challenge requires inbound connections on port 443. Check your firewall and cloud provider security groups.
3. **Cloudflare proxy enabled:** If using Cloudflare with orange cloud (proxy), the TLS challenge may fail. Try grey cloud (DNS only) during setup.

Use the staging server while debugging to avoid rate limits. Uncomment the `caserver` line in your compose file.
</Accordion>

<Accordion label="Too many certificates already issued" group="troubleshooting">
Let's Encrypt rate limits: 5 duplicate certificates per domain per week. If you've been debugging, you may have hit this.

Fix: Switch to the staging server while testing:

```yaml
- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
```

Staging certs show browser warnings but let you verify the setup works. Switch back to production once everything is confirmed.
</Accordion>

<Accordion label="Docker containers not discovered by Traefik" group="troubleshooting">
Check two things:

1. The container is on the `traefik-net` network:
```sh
docker network inspect traefik-net
```
Your container should appear in the output.

2. The container has the `traefik.enable=true` label:
```sh
docker inspect your-container | grep -i traefik.enable
```
</Accordion>

<Accordion label="Dashboard shows 401 / auth not working" group="troubleshooting">
The most common cause is a single-`$` hash in `.env` getting mangled by Docker Compose interpolation. Regenerate with the `$$` escaping from Step 7:

```sh
echo "TRAEFIK_DASHBOARD_CREDENTIALS=$(htpasswd -nbB admin 'YourPassword123' | sed 's/\$/$$/g')" | sudo tee .env
```

Make sure the `.env` is in the same directory as the compose file and that the `env_file: .env` directive is present. Also verify the middleware label references `traefik-auth` correctly.
</Accordion>

<Accordion label="HTTP redirect loop" group="troubleshooting">
If you're behind Cloudflare, the most common cause is the SSL/TLS mode set to "Flexible" instead of "Full" (or "Full (strict)"). Cloudflare "Flexible" connects to your origin over HTTP, Traefik redirects to HTTPS, Cloudflare connects over HTTP again, creating an infinite loop.

Fix: In Cloudflare dashboard, go to SSL/TLS and set it to **Full** (or **Full (strict)** if you have a valid cert).
</Accordion>

<Notice type="info">
Use the Let's Encrypt staging server while testing to avoid hitting rate limits. Uncomment the caserver line in your compose file, and switch back to production once everything works.
</Notice>

## Hardening and production notes

Once your basic setup works, here's what to do for production:

<ListCheck>
<ul>
<li>**Socket proxy** (already in recommended setup). It limits Traefik's Docker API access to read-only container/network/service info</li>
<li>**Security headers middleware**: add HSTS, content-type sniffing protection, XSS filter</li>
<li>**Rate limiting middleware**: protect the dashboard and apps from abuse</li>
<li>**Backup `acme.json`**: losing it means re-issuing all certificates (and hitting rate limits). Back it up periodically or use a Docker volume to persistent storage</li>
<li>**Use Docker secrets** for credentials instead of `.env` files in production</li>
<li>**Keep Traefik updated**. `traefik:v3.7` tracks the v3.7.x patch releases. Check for new minor versions periodically</li>
</ul>
</ListCheck>

**Security headers middleware**: add these labels to your app containers:

```yaml
- traefik.http.middlewares.sec-headers.headers.sslredirect=true
- traefik.http.middlewares.sec-headers.headers.stsseconds=63072000
- traefik.http.middlewares.sec-headers.headers.stsincludeSubdomains=true
- traefik.http.middlewares.sec-headers.headers.stspreload=true
- traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.sec-headers.headers.browserXssFilter=true
```

**Rate limiting middleware**: add to your router:

```yaml
- traefik.http.middlewares.rate-limit.ratelimit.average=100
- traefik.http.middlewares.rate-limit.ratelimit.burst=50
- traefik.http.routers.traefik-secure.middlewares=traefik-auth,rate-limit
```

<Notice type="info">
Explore community plugins at <a href="https://plugins.traefik.io/">plugins.traefik.io</a> for CrowdSec integration, geo filtering, and more. For broader VPS hardening, see: <a href="https://www.bitdoze.com/crowdsec-secure-server/">secure your VPS with CrowdSec</a>
</Notice>

## Conclusions

Setting up Traefik as a docker reverse proxy for your self-hosted apps is straightforward once you go through the steps. We covered VPS creation, Docker install, Traefik v3.7 with automatic Let's Encrypt TLS certificates, dashboard security with bcrypt auth, a socket proxy for reduced blast radius, and deploying your first app with Traefik labels.

The key improvements over a bare-bones setup: socket proxy limits Docker API exposure, `acme.json` with proper permissions prevents the most common beginner failure, healthchecks catch problems early, and bcrypt auth with the `$$` escaping handled in Step 7 keeps the dashboard locked down.

Once Traefik is running, adding new services is just a matter of adding labels to a Docker container and putting it on `traefik-net`. Dockge makes managing those compose files even easier from a web UI.

<Button text="Best Docker Containers for Your Home Server" link="/docker-containers-home-server/" variant="solid" color="blue" size="md" icon="arrow-right" />