Skip to content

Tutorial on Configuring Traefik on a Cloud Server

Use Traefik, Docker Compose, and Let's Encrypt to set up a unified entry point on a cloud server, enabling multiple web services to be quickly integrated via domain names, HTTPS, and container labels.

Filed under
Infrastructure
Reading Time
21 min read
Updated
2026.08.04

In short

By placing Traefik at the entry point of the cloud server to centrally handle reverse proxying, Let’s Encrypt certificates, and routing, business containers can be integrated simply by joining the same Docker network and using labels to specify their domain names and ports. The server only exposes ports 22, 80, and 443, so there’s no need to modify the Nginx configuration when adding new services.

Tutorial on Configuring Traefik on a Cloud Server: Setting Up a Reusable HTTPS Entry Point with Docker Compose

If you have a cloud server and plan to gradually deploy a blog, backend, API, file service, and WebSocket service on it, you’ll eventually run into a problem: each service requires you to manage its own ports, domain names, HTTPS certificates, and forwarding rules, which can quickly become a mess to maintain.

My approach is to place Traefik at the server’s entry point, where it handles reverse proxying, HTTPS certificates, and traffic routing. Business containers simply need to be added to the same Docker network and use labels to specify their domain names, entry points, certificate resolvers, and internal ports—after which they can be proxied uniformly.

This article isn’t just a list of commands; it shares a reusable approach for deploying Traefik on cloud servers. You can start by using the whoami service to verify connectivity, then gradually integrate your own Spring Boot, Node.js, Nginx, WebSocket, gRPC, Gitea SSH, and other services.

Suitable Scenarios

This article assumes you already meet the following conditions:

  • Your cloud server has a public IP address.
  • You have purchased a domain name and can configure DNS resolution.
  • You use Docker Compose to deploy services on the server.
  • You want to use Let’s Encrypt to automatically issue HTTPS certificates.
  • Services primarily run as Docker containers.

Here’s what the final setup looks like:

用户浏览器
    |
    | HTTPS / TCP
    v
Traefik
    |
    | Docker network: traefik
    v
业务容器:blog / api / file / whoami / gitea ...

Traefik’s core value lies in having a single entry point, with rules following the containers. When adding new services in the future, you won’t need to modify a bunch of Nginx configuration files—simply add labels to the containers.

TIP

If you’re just running a service temporarily, Nginx can certainly handle it. But if services will continue to be added to a server, Traefik’s Docker provider will make future maintenance much easier.

Overall Solution

This solution consists of several parts:

  1. The server only exposes necessary ports: 22, 80, 443, and TCP service ports as needed.
  2. Traefik uses the Docker provider to read container labels.
  3. All proxied containers are added to the same external Docker network: traefik.
  4. HTTPS certificates are automatically issued by Let’s Encrypt.
  5. The dashboard is accessible only via domain name, HTTPS, and Basic Auth.
  6. Cloudflare users can opt for the DNS-01 challenge to easily enable Cloudflare proxy and wildcard certificates.

I recommend placing Traefik in /opt/traefik:

/opt/traefik
├── .env
├── docker-compose.yml
├── whoami.yml
└── letsencrypt
    └── acme.json

where acme.json stores certificate information, and .env stores sensitive configurations such as domain names, email addresses, Basic Auth credentials, and the Cloudflare token.

Preparing the Server Environment

First, log in to the server:

ssh root@你的服务器IP

Update the system and install common tools:

apt update && apt upgrade -y
apt install -y curl vim ufw apache2-utils dnsutils

If Docker is not yet installed on the server, use the official installation script:

curl -fsSL https://get.docker.com | sh

Verify that Docker and Compose are available:

docker version
docker compose version

Then configure the firewall:

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status

WARNING

Do not directly expose the Traefik Dashboard’s 8080 port to the public internet. The Dashboard should be accessed via a domain name, HTTPS, and Basic Auth.

Configure Domain Name Resolution

First, add an A record in your DNS provider to point the domain name to the server’s public IP address.

Record NameTypeValue

| traefik.example.com

| A | Server’s public IP | | whoami.example.com | A | Server’s public IP | | app.example.com | A | Server’s public IP |

Replace example.com with your own domain name.

Verify that the DNS resolution is working:

Continue configuring Traefik only after ```bash dig traefik.example.com +short dig whoami.example.com +short


returns the server’s public IP.

### Options for Cloudflare Users

If your domain is hosted on Cloudflare, you can first connect the domain to Cloudflare and then manage the DNS records using Cloudflare.

Basic steps:

1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com/).
2. Click `Add a domain` and enter the root domain, such as `example.com`.
3. Select a plan; the free plan is usually sufficient.
4. Verify that the DNS records detected by Cloudflare are correct.
5. Go to your domain registrar’s control panel and change the NS servers to the two nameservers provided by Cloudflare.
6. Wait until Cloudflare displays the domain status as `Active`.

The recommended DNS records are as follows:

| Record Name    | Type | Value            | Proxy Status        |
| --------- | ---- | ------------- | ------------------- |
| `traefik`

| A    | Server’s public IP | DNS only or Proxied |
| `whoami`   | A    | Server’s public IP | DNS only or Proxied |
| `app`      | A    | Server’s public IP | DNS only or Proxied |

If using the HTTP-01 certificate challenge, it is recommended to set this to `DNS only` during the debugging phase to avoid interference from the Cloudflare proxy layer. Once the certificate has been issued and the access path is functioning normally, enable `Proxied` as needed.

It is recommended to set Cloudflare’s SSL/TLS mode to:

 Overview -> Full (strict)
```text
SSL/TLS -> Overview -> Full (strict)

Full (strict) requires that the origin server also have a valid certificate, which is provided by Traefik and Let’s Encrypt.

Create the Traefik Working Directory

The permissions for ```bash mkdir -p /opt/traefik/letsencrypt cd /opt/traefik touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json


`acme.json` must be tightened; otherwise, Traefik may refuse to use this file.

Next, create an external Docker network:

```bash
docker network create traefik

All service containers that will be proxied by Traefik must be added to this network.

Generate a Dashboard Login Password

Basic Auth must be enabled for the Dashboard. First, generate a username and password: The

htpasswd -nbB admin '换成你的强密码'

command will output something like:

admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

In the Docker Compose label, ````text 用户浏览器 | | HTTPS / TCP v Traefik | | Docker network: traefik v 业务容器:blog / api / file / whoami / gitea ...


Traefik’s core value lies in having a single entry point, with rules following the containers. When adding new services in the future, you won’t need to modify a bunch of Nginx configuration files—simply add labels to the containers.

> [!tip]
> If you’re just running a service temporarily, Nginx can certainly handle it. But if services will continue to be added to a server, Traefik’s Docker provider will make future maintenance much easier.

## Overall Solution

This solution consists of several parts:

1. The server only exposes necessary ports: `22`, `80`, `443`, and TCP service ports as needed.
2. Traefik uses the Docker provider to read container labels.
3. All proxied containers are added to the same external Docker network: `traefik`.
4. HTTPS certificates are automatically issued by Let’s Encrypt.
5. The dashboard is accessible only via domain name, HTTPS, and Basic Auth.
6. Cloudflare users can opt for the DNS-01 challenge to easily enable Cloudflare proxy and wildcard certificates.

I recommend placing Traefik in `/opt/traefik`:

```text
/opt/traefik
├── .env
├── docker-compose.yml
├── whoami.yml
└── letsencrypt
    └── acme.json

where acme.json stores certificate information, and .env stores sensitive configurations such as domain names, email addresses, Basic Auth credentials, and the Cloudflare token.

Preparing the Server Environment

First, log in to the server:

ssh root@你的服务器IP

Update the system and install common tools:

apt update && apt upgrade -y
apt install -y curl vim ufw apache2-utils dnsutils

If Docker is not yet installed on the server, use the official installation script:

curl -fsSL https://get.docker.com | sh

Verify that Docker and Compose are available:

docker version
docker compose version

Then configure the firewall:

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status

WARNING

Do not directly expose the Traefik Dashboard’s 8080 port to the public internet. The Dashboard should be accessed via a domain name, HTTPS, and Basic Auth.

Configure Domain Name Resolution

First, add an A record in your DNS provider to point the domain name to the server’s public IP address.

Record NameTypeValue

| traefik.example.com

| A | Server’s public IP | | whoami.example.com | A | Server’s public IP | | app.example.com | A | Server’s public IP |

Replace example.com with your own domain name.

Verify that the DNS resolution is working:

Continue configuring Traefik only after ```bash dig traefik.example.com +short dig whoami.example.com +short


returns the server’s public IP.

### Options for Cloudflare Users

If your domain is hosted on Cloudflare, you can first connect the domain to Cloudflare and then manage the DNS records using Cloudflare.

Basic steps:

1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com/).
2. Click `Add a domain` and enter the root domain, such as `example.com`.
3. Select a plan; the free plan is usually sufficient.
4. Verify that the DNS records detected by Cloudflare are correct.
5. Go to your domain registrar’s control panel and change the NS servers to the two nameservers provided by Cloudflare.
6. Wait until Cloudflare displays the domain status as `Active`.

The recommended DNS records are as follows:

| Record Name    | Type | Value            | Proxy Status        |
| --------- | ---- | ------------- | ------------------- |
| `traefik`

| A    | Server’s public IP | DNS only or Proxied |
| `whoami`   | A    | Server’s public IP | DNS only or Proxied |
| `app`      | A    | Server’s public IP | DNS only or Proxied |

If using the HTTP-01 certificate challenge, it is recommended to set this to `DNS only` during the debugging phase to avoid interference from the Cloudflare proxy layer. Once the certificate has been issued and the access path is functioning normally, enable `Proxied` as needed.

It is recommended to set Cloudflare’s SSL/TLS mode to:

 Overview -> Full (strict)
```text
SSL/TLS -> Overview -> Full (strict)

Full (strict) requires that the origin server also have a valid certificate, which is provided by Traefik and Let’s Encrypt.

Create the Traefik Working Directory

The permissions for ```bash mkdir -p /opt/traefik/letsencrypt cd /opt/traefik touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json


`acme.json` must be tightened; otherwise, Traefik may refuse to use this file.

Next, create an external Docker network:

```bash
docker network create traefik

All service containers that will be proxied by Traefik must be added to this network.

Generate a Dashboard Login Password

Basic Auth must be enabled for the Dashboard. First, generate a username and password: The

htpasswd -nbB admin '换成你的强密码'

command will output something like:

admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

In the Docker Compose label, `

Tutorial on Configuring Traefik on a Cloud Server: Setting Up a Reusable HTTPS Entry Point with Docker Compose

If you have a cloud server and plan to gradually deploy a blog, backend, API, file service, and WebSocket service on it, you’ll eventually run into a problem: each service requires you to manage its own ports, domain names, HTTPS certificates, and forwarding rules, which can quickly become a mess to maintain.

My approach is to place Traefik at the server’s entry point, where it handles reverse proxying, HTTPS certificates, and traffic routing. Business containers simply need to be added to the same Docker network and use labels to specify their domain names, entry points, certificate resolvers, and internal ports—after which they can be proxied uniformly.

This article isn’t just a list of commands; it shares a reusable approach for deploying Traefik on cloud servers. You can start by using the whoami service to verify connectivity, then gradually integrate your own Spring Boot, Node.js, Nginx, WebSocket, gRPC, Gitea SSH, and other services.

Suitable Scenarios

This article assumes you already meet the following conditions:

  • Your cloud server has a public IP address.
  • You have purchased a domain name and can configure DNS resolution.
  • You use Docker Compose to deploy services on the server.
  • You want to use Let’s Encrypt to automatically issue HTTPS certificates.
  • Services primarily run as Docker containers.

Here’s what the final setup looks like:

用户浏览器
    |
    | HTTPS / TCP
    v
Traefik
    |
    | Docker network: traefik
    v
业务容器:blog / api / file / whoami / gitea ...

Traefik’s core value lies in having a single entry point, with rules following the containers. When adding new services in the future, you won’t need to modify a bunch of Nginx configuration files—simply add labels to the containers.

TIP

If you’re just running a service temporarily, Nginx can certainly handle it. But if services will continue to be added to a server, Traefik’s Docker provider will make future maintenance much easier.

Overall Solution

This solution consists of several parts:

  1. The server only exposes necessary ports: 22

, 80

, 443

, and TCP service ports as needed. 2. Traefik uses the Docker provider to read container labels. 3. All proxied containers are added to the same external Docker network: traefik

. 4. HTTPS certificates are automatically issued by Let’s Encrypt. 5. The dashboard is accessible only via domain name, HTTPS, and Basic Auth. 6. Cloudflare users can opt for the DNS-01 challenge to easily enable Cloudflare proxy and wildcard certificates.

I recommend placing Traefik in /opt/traefik

:

/opt/traefik
├── .env
├── docker-compose.yml
├── whoami.yml
└── letsencrypt
    └── acme.json

where acme.json

stores certificate information, and .env

stores sensitive configurations such as domain names, email addresses, Basic Auth credentials, and the Cloudflare token.

Preparing the Server Environment

First, log in to the server:

ssh root@你的服务器IP

Update the system and install common tools:

apt update && apt upgrade -y
apt install -y curl vim ufw apache2-utils dnsutils

If Docker is not yet installed on the server, use the official installation script:

curl -fsSL https://get.docker.com | sh

Verify that Docker and Compose are available:

docker version
docker compose version

Then configure the firewall:

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status

WARNING

Do not directly expose the Traefik Dashboard’s 8080

port to the public internet. The Dashboard should be accessed via a domain name, HTTPS, and Basic Auth.

Configure Domain Name Resolution

First, add an A record in your DNS provider to point the domain name to the server’s public IP address.

Record NameTypeValue

| traefik.example.com

| A | Server’s public IP | | whoami.example.com

| A | Server’s public IP | | app.example.com

| A | Server’s public IP |

Replace example.com

with your own domain name.

Verify that the DNS resolution is working:

Continue configuring Traefik only after ```bash dig traefik.example.com +short dig whoami.example.com +short


returns the server’s public IP.

### Options for Cloudflare Users

If your domain is hosted on Cloudflare, you can first connect the domain to Cloudflare and then manage the DNS records using Cloudflare.

Basic steps:

1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com/

).
2. Click `Add a domain`

and enter the root domain, such as `example.com`

.
3. Select a plan; the free plan is usually sufficient.
4. Verify that the DNS records detected by Cloudflare are correct.
5. Go to your domain registrar’s control panel and change the NS servers to the two nameservers provided by Cloudflare.
6. Wait until Cloudflare displays the domain status as `Active`

.

The recommended DNS records are as follows:

| Record Name    | Type | Value            | Proxy Status        |
| --------- | ---- | ------------- | ------------------- |
| `traefik`

| A    | Server’s public IP | DNS only or Proxied |
| `whoami`

  | A    | Server’s public IP | DNS only or Proxied |
| `app`

     | A    | Server’s public IP | DNS only or Proxied |

If using the HTTP-01 certificate challenge, it is recommended to set this to `DNS only`

during the debugging phase to avoid interference from the Cloudflare proxy layer. Once the certificate has been issued and the access path is functioning normally, enable `Proxied`

as needed.

It is recommended to set Cloudflare’s SSL/TLS mode to:

 Overview -> Full (strict)
```text
SSL/TLS -> Overview -> Full (strict)

Full (strict)

requires that the origin server also have a valid certificate, which is provided by Traefik and Let’s Encrypt.

Create the Traefik Working Directory

The permissions for ```bash mkdir -p /opt/traefik/letsencrypt cd /opt/traefik touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json


`acme.json`

must be tightened; otherwise, Traefik may refuse to use this file.

Next, create an external Docker network:

```bash
docker network create traefik

All service containers that will be proxied by Traefik must be added to this network.

Generate a Dashboard Login Password

Basic Auth must be enabled for the Dashboard. First, generate a username and password: The

htpasswd -nbB admin '换成你的强密码'

command will output something like:

admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

In the Docker Compose label, 需要转义成$

Tutorial on Configuring Traefik on a Cloud Server: Setting Up a Reusable HTTPS Entry Point with Docker Compose

If you have a cloud server and plan to gradually deploy a blog, backend, API, file service, and WebSocket service on it, you’ll eventually run into a problem: each service requires you to manage its own ports, domain names, HTTPS certificates, and forwarding rules, which can quickly become a mess to maintain.

My approach is to place Traefik at the server’s entry point, where it handles reverse proxying, HTTPS certificates, and traffic routing. Business containers simply need to be added to the same Docker network and use labels to specify their domain names, entry points, certificate resolvers, and internal ports—after which they can be proxied uniformly.

This article isn’t just a list of commands; it shares a reusable approach for deploying Traefik on cloud servers. You can start by using the whoami

service to verify connectivity, then gradually integrate your own Spring Boot, Node.js, Nginx, WebSocket, gRPC, Gitea SSH, and other services.

Suitable Scenarios

This article assumes you already meet the following conditions:

  • Your cloud server has a public IP address.
  • You have purchased a domain name and can configure DNS resolution.
  • You use Docker Compose to deploy services on the server.
  • You want to use Let’s Encrypt to automatically issue HTTPS certificates.
  • Services primarily run as Docker containers.

Here’s what the final setup looks like:

用户浏览器
    |
    | HTTPS / TCP
    v
Traefik
    |
    | Docker network: traefik
    v
业务容器:blog / api / file / whoami / gitea ...

Traefik’s core value lies in having a single entry point, with rules following the containers. When adding new services in the future, you won’t need to modify a bunch of Nginx configuration files—simply add labels to the containers.

TIP

If you’re just running a service temporarily, Nginx can certainly handle it. But if services will continue to be added to a server, Traefik’s Docker provider will make future maintenance much easier.

Overall Solution

This solution consists of several parts:

  1. The server only exposes necessary ports: 22

, 80

, 443

, and TCP service ports as needed. 2. Traefik uses the Docker provider to read container labels. 3. All proxied containers are added to the same external Docker network: traefik

. 4. HTTPS certificates are automatically issued by Let’s Encrypt. 5. The dashboard is accessible only via domain name, HTTPS, and Basic Auth. 6. Cloudflare users can opt for the DNS-01 challenge to easily enable Cloudflare proxy and wildcard certificates.

I recommend placing Traefik in /opt/traefik

:

/opt/traefik
├── .env
├── docker-compose.yml
├── whoami.yml
└── letsencrypt
    └── acme.json

where acme.json

stores certificate information, and .env

stores sensitive configurations such as domain names, email addresses, Basic Auth credentials, and the Cloudflare token.

Preparing the Server Environment

First, log in to the server:

ssh root@你的服务器IP

Update the system and install common tools:

apt update && apt upgrade -y
apt install -y curl vim ufw apache2-utils dnsutils

If Docker is not yet installed on the server, use the official installation script:

curl -fsSL https://get.docker.com | sh

Verify that Docker and Compose are available:

docker version
docker compose version

Then configure the firewall:

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status

WARNING

Do not directly expose the Traefik Dashboard’s 8080

port to the public internet. The Dashboard should be accessed via a domain name, HTTPS, and Basic Auth.

Configure Domain Name Resolution

First, add an A record in your DNS provider to point the domain name to the server’s public IP address.

Record NameTypeValue

| traefik.example.com

| A | Server’s public IP | | whoami.example.com

| A | Server’s public IP | | app.example.com

| A | Server’s public IP |

Replace example.com

with your own domain name.

Verify that the DNS resolution is working:

Continue configuring Traefik only after ```bash dig traefik.example.com +short dig whoami.example.com +short


returns the server’s public IP.

### Options for Cloudflare Users

If your domain is hosted on Cloudflare, you can first connect the domain to Cloudflare and then manage the DNS records using Cloudflare.

Basic steps:

1. Log in to the [Cloudflare Dashboard](https://dash.cloudflare.com/

).
2. Click `Add a domain`

and enter the root domain, such as `example.com`

.
3. Select a plan; the free plan is usually sufficient.
4. Verify that the DNS records detected by Cloudflare are correct.
5. Go to your domain registrar’s control panel and change the NS servers to the two nameservers provided by Cloudflare.
6. Wait until Cloudflare displays the domain status as `Active`

.

The recommended DNS records are as follows:

| Record Name    | Type | Value            | Proxy Status        |
| --------- | ---- | ------------- | ------------------- |
| `traefik`

| A    | Server’s public IP | DNS only or Proxied |
| `whoami`

  | A    | Server’s public IP | DNS only or Proxied |
| `app`

     | A    | Server’s public IP | DNS only or Proxied |

If using the HTTP-01 certificate challenge, it is recommended to set this to `DNS only`

during the debugging phase to avoid interference from the Cloudflare proxy layer. Once the certificate has been issued and the access path is functioning normally, enable `Proxied`

as needed.

It is recommended to set Cloudflare’s SSL/TLS mode to:

 Overview -> Full (strict)
```text
SSL/TLS -> Overview -> Full (strict)

Full (strict)

requires that the origin server also have a valid certificate, which is provided by Traefik and Let’s Encrypt.

Create the Traefik Working Directory

The permissions for ```bash mkdir -p /opt/traefik/letsencrypt cd /opt/traefik touch letsencrypt/acme.json chmod 600 letsencrypt/acme.json


`acme.json`

must be tightened; otherwise, Traefik may refuse to use this file.

Next, create an external Docker network:

```bash
docker network create traefik

All service containers that will be proxied by Traefik must be added to this network.

Generate a Dashboard Login Password

Basic Auth must be enabled for the Dashboard. First, generate a username and password: The

htpasswd -nbB admin '换成你的强密码'

command will output something like:

admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

In the Docker Compose label,

. If you place the password hash in .env

and enclose it in single quotes, you can preserve the original format:

TRAEFIK_BASIC_AUTH_USERS='admin:$2y$05$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

Writing the Traefik Configuration

First, create /opt/traefik/.env

:

TRAEFIK_IMAGE=traefik:v3.6
TRAEFIK_DOMAIN=traefik.example.com
ACME_EMAIL=你的邮箱@example.com
TRAEFIK_BASIC_AUTH_USERS='admin:$2y$05$替换成你生成的密码哈希'

If using the Cloudflare DNS-01 challenge, add the following:

CF_DNS_API_TOKEN=替换成你的Cloudflare_API_Token

Restrict the permissions for .env

:

chmod 600 /opt/traefik/.env

WARNING

.env

in a public repository, and do not post the actual token in notes, group chats, or screenshots. Although the Basic Auth hash is not a plaintext password, it is still recommended to treat it as sensitive information.

HTTP-01 Version: Suitable for Most Beginner Scenarios

If you’re not using Cloudflare, or if you just want to get the basic setup working first, you can use HTTP-01. It requires that port 80

on the server be accessible from the public internet.

Create /opt/traefik/docker-compose.yml

:

services:
  traefik:
    image: ${TRAEFIK_IMAGE}
    container_name: traefik
    restart: unless-stopped
    env_file:
      - .env
    command:
      - '--api.dashboard=true'
      - '--providers.docker=true'
      - '--providers.docker.exposedbydefault=false'
      - '--providers.docker.network=traefik'
      - '--entrypoints.web.address=:80'
      - '--entrypoints.websecure.address=:443'
      - '--entrypoints.ssh.address=:2222'
      - '--entrypoints.web.http.redirections.entrypoint.to=websecure'
      - '--entrypoints.web.http.redirections.entrypoint.scheme=https'
      - '--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}'
      - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
      - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
    ports:
      - '80:80'
      - '443:443'
      - '2222:2222'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DOMAIN}`)'
      - 'traefik.http.routers.traefik.entrypoints=websecure'
      - 'traefik.http.routers.traefik.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.traefik.service=api@internal'
      - 'traefik.http.routers.traefik.middlewares=traefik-auth'
      - 'traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_BASIC_AUTH_USERS}'

networks:
  traefik:
    external: true

Here are a few key points:

  • exposedbydefault=false

: By default, no containers are exposed; you must explicitly add traefik.enable=true

.

  • providers.docker.network=traefik

: Tells Traefik to prioritize this Docker network when accessing backend containers.

  • web

and websecure

: Correspond to 80

and 443

, respectively.

  • entrypoints.web.http.redirections...

: Automatically redirects HTTP to HTTPS.

  • api@internal

: Exposes Traefik’s own Dashboard as an internal service, which is then bound to the domain via the router.

DNS-01 Version: Suitable for Cloudflare and Wildcard Certificates

If your domain is hosted on Cloudflare, we recommend using DNS-01. It has three advantages:

  • It does not rely on port 80

for certificate verification.

  • You can enable Cloudflare’s Orange Cloud proxy.
  • You can apply for a wildcard certificate, such as *.example.com

.

First, create a Cloudflare API token:

  1. Go to [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens

). 2. Click Create Token

. 3. You can choose the Edit zone DNS

template or create a custom token. 4. Permissions must include at least Zone / Zone / Read

and Zone / DNS / Edit

. 5. Limit Zone Resources to the domain you need to manage, such as example.com

. 6. After creation, copy the token and paste it into CF_DNS_API_TOKEN

in /opt/traefik/.env

.

Then replace the ACME challenge-related commands with DNS-01:

command:
  - '--api.dashboard=true'
  - '--providers.docker=true'
  - '--providers.docker.exposedbydefault=false'
  - '--providers.docker.network=traefik'
  - '--entrypoints.web.address=:80'
  - '--entrypoints.websecure.address=:443'
  - '--entrypoints.ssh.address=:2222'
  - '--entrypoints.web.http.redirections.entrypoint.to=websecure'
  - '--entrypoints.web.http.redirections.entrypoint.scheme=https'
  - '--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}'
  - '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
  - '--certificatesresolvers.letsencrypt.acme.dnschallenge=true'
  - '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare'
  - '--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53'

. If a service requires a wildcard certificate, add the following to the corresponding router:

labels:
  - 'traefik.http.routers.app.tls.domains[0].main=example.com'
  - 'traefik.http.routers.app.tls.domains[0].sans=*.example.com'

NOTE

Traefik’s DNS-01 functionality relies on Lego at the core. The Cloudflare provider supports CF_DNS_API_TOKEN

and also supports splitting “Zone Read” and “DNS Edit” into two more granular permission tokens: CF_ZONE_API_TOKEN

and CF_DNS_API_TOKEN

.

Start Traefik

cd /opt/traefik
docker compose up -d
docker compose logs -f traefik

Then visit:

https://traefik.example.com/dashboard/

If you see a Basic Auth login prompt and can view the Dashboard after logging in, it means the entry proxy is running successfully.

TIP

Certificate issuance may take a while on the first startup. When troubleshooting, check docker compose logs -f traefik

first; avoid restarting too many times to prevent triggering Let’s Encrypt’s rate limits.

Verify the Proxy Connection with whoami

Before integrating with production services, I usually deploy a test service at whoami

first. It’s simple enough to quickly verify that the domain, certificate, routing, and Docker network are all functioning correctly.

Create /opt/traefik/whoami.yml

:

services:
  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.whoami.rule=Host(`whoami.example.com`)'
      - 'traefik.http.routers.whoami.entrypoints=websecure'
      - 'traefik.http.routers.whoami.tls.certresolver=letsencrypt'
      - 'traefik.http.services.whoami.loadbalancer.server.port=80'

networks:
  traefik:
    external: true

Start the test service:

docker compose -f /opt/traefik/whoami.yml up -d

Verify:

curl -I https://whoami.example.com

If the response is HTTP/2 200

or HTTP/1.1 200 OK

, the HTTPS proxy is working properly.

Integrating Your Own Business Services

There are only three key steps for integrating business services with Traefik:

  1. Add the service to the traefik

external network. 2. Configure traefik.enable=true

. 3. Use labels to declare the domain name, entry point, certificate resolver, and container internal port.

Minimal example:

services:
  app:
    image: your-app:latest
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.app.rule=Host(`app.example.com`)'
      - 'traefik.http.routers.app.entrypoints=websecure'
      - 'traefik.http.routers.app.tls.certresolver=letsencrypt'
      - 'traefik.http.services.app.loadbalancer.server.port=8080'

networks:
  traefik:
    external: true

loadbalancer.server.port=8080

refers to the container’s internal listening port, not the host machine’s port. After integrating with Traefik, business containers typically no longer need to specify:

ports:
  - '8080:8080'

unless this port needs to be exposed directly, bypassing Traefik.

Common Service Integration Templates

Standard Web Services

Spring Boot, Node.js, Nginx, static frontend sites, and admin dashboards can all use this type of template:

services:
  app:
    image: your-app:latest
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.app.rule=Host(`app.example.com`)'
      - 'traefik.http.routers.app.entrypoints=websecure'
      - 'traefik.http.routers.app.tls.certresolver=letsencrypt'
      - 'traefik.http.services.app.loadbalancer.server.port=8080'

networks:
  traefik:
    external: true

File Upload Services

By default, Traefik does not use client_max_body_size

to limit upload sizes like Nginx does.Actual upload size limits are typically enforced by the business application, Nginx, Spring Boot, the gateway, the object storage SDK, or Cloudflare.

If you want to actively limit upload sizes at the Traefik layer, you can use the buffering

middleware:

services:
  file-api:
    image: your-file-api:latest
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.file-api.rule=Host(`file.example.com`)'
      - 'traefik.http.routers.file-api.entrypoints=websecure'
      - 'traefik.http.routers.file-api.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.file-api.middlewares=file-upload-limit'
      - 'traefik.http.middlewares.file-upload-limit.buffering.maxRequestBodyBytes=104857600'
      - 'traefik.http.middlewares.file-upload-limit.buffering.memRequestBodyBytes=10485760'
      - 'traefik.http.services.file-api.loadbalancer.server.port=8080'

networks:
  traefik:
    external: true

Explanation of the configuration above:

  • maxRequestBodyBytes=104857600

: Maximum upload request body size is 100 MB.

  • memRequestBodyBytes=10485760

: Do not load the entire body into memory once it exceeds 10 MB.

WARNING

If Cloudflare’s Orange Cloud proxy is enabled, you must also be aware of Cloudflare’s own upload size limits. For large file uploads, it is recommended to have the client upload directly to object storage—such as S3, R2, or OSS—and then have the business service save the file URL.

File Download Services

For standard download services, you can use the HTTP router directly. When downloading large files, the key is to prevent the application layer from loading the entire file into memory at once.

services:
  download:
    image: nginx:alpine
    restart: unless-stopped
    volumes:
      - ./downloads:/usr/share/nginx/html:ro
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.download.rule=Host(`download.example.com`)'
      - 'traefik.http.routers.download.entrypoints=websecure'
      - 'traefik.http.routers.download.tls.certresolver=letsencrypt'
      - 'traefik.http.services.download.loadbalancer.server.port=80'

networks:
  traefik:
    external: true

If you need to limit the size of the download response, you can also use buffering.maxResponseBodyBytes

:

labels:
  - 'traefik.http.routers.download.middlewares=download-limit'
  - 'traefik.http.middlewares.download-limit.buffering.maxResponseBodyBytes=524288000'

It is not recommended to arbitrarily set maxResponseBodyBytes

for large file downloads, as Traefik will reject requests that exceed the limit. In most cases, limits should be enforced through business authentication, signed object storage URLs, or CDN rules.

WebSocket Services

Traefik automatically handles the HTTP Upgrade for WebSockets; generally, no additional configuration of the Upgrade

or Connection

request headers is required.

services:
  ws-app:
    image: your-ws-app:latest
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.ws-app.rule=Host(`ws.example.com`)'
      - 'traefik.http.routers.ws-app.entrypoints=websecure'
      - 'traefik.http.routers.ws-app.tls.certresolver=letsencrypt'
      - 'traefik.http.services.ws-app.loadbalancer.server.port=3000'

networks:
  traefik:
    external: true

Example of a client connection:

const socket = new WebSocket('wss://ws.example.com/socket');

If WebSockets and regular APIs are on the same domain, you can route them separately by path:

labels:
  - 'traefik.http.routers.app-api.rule=Host(`app.example.com`) && PathPrefix(`/api`)'
  - 'traefik.http.routers.app-api.entrypoints=websecure'
  - 'traefik.http.routers.app-api.tls.certresolver=letsencrypt'
  - 'traefik.http.routers.app-api.service=app-api'
  - 'traefik.http.services.app-api.loadbalancer.server.port=8080'
  - 'traefik.http.routers.app-ws.rule=Host(`app.example.com`) && PathPrefix(`/socket`)'
  - 'traefik.http.routers.app-ws.entrypoints=websecure'
  - 'traefik.http.routers.app-ws.tls.certresolver=letsencrypt'
  - 'traefik.http.routers.app-ws.service=app-ws'
  - 'traefik.http.services.app-ws.loadbalancer.server.port=3000'

NOTE

When Cloudflare proxies WebSockets, you must also enable WebSockets on the Cloudflare side. Most accounts support this by default, but if connections frequently drop, you should check your Cloudflare rules, server heartbeats, and application timeout settings.

gRPC Services

gRPC uses HTTP/2. When Traefik handles HTTPS and forwards it to the backend gRPC service, you typically need to specify the service scheme as h2c

.

services:
  grpc-api:
    image: your-grpc-api:latest
    restart: unless-stopped
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.grpc-api.rule=Host(`grpc.example.com`)'
      - 'traefik.http.routers.grpc-api.entrypoints=websecure'
      - 'traefik.http.routers.grpc-api.tls.certresolver=letsencrypt'
      - 'traefik.http.services.grpc-api.loadbalancer.server.scheme=h2c'
      - 'traefik.http.services.grpc-api.loadbalancer.server.port=50051'

networks:
  traefik:
    external: true

SSH Services

SSH is not HTTP and cannot use the traefik.http.*

label; a TCP router must be used instead. Since the host machine typically already occupies port 22

, it is recommended that Traefik use 2222

for external connections, while still forwarding internally within the container to 22

.

The Traefik main configuration must include an SSH entrypoint:

command:
  - '--entrypoints.ssh.address=:2222'
ports:
  - '2222:2222'

. Gitea SSH example:

services:
  gitea:
    image: gitea/gitea:latest
    restart: unless-stopped
    networks:
      - traefik
    volumes:
      - ./gitea:/data
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.gitea.rule=Host(`git.example.com`)'
      - 'traefik.http.routers.gitea.entrypoints=websecure'
      - 'traefik.http.routers.gitea.tls.certresolver=letsencrypt'
      - 'traefik.http.services.gitea.loadbalancer.server.port=3000'
      - 'traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)'
      - 'traefik.tcp.routers.gitea-ssh.entrypoints=ssh'
      - 'traefik.tcp.routers.gitea-ssh.service=gitea-ssh'
      - 'traefik.tcp.services.gitea-ssh.loadbalancer.server.port=22'

networks:
  traefik:
    external: true

. Connection method:

ssh -p 2222 git@git.example.com
git clone ssh://git@git.example.com:2222/用户名/仓库名.git

WARNING

The HostSNI

of a TCP router can only be distinguished by domain name within a TLS connection. Since SSH itself does not support SNI, the common practice is to have one SSH entrypoint correspond to one SSH service; if there are multiple SSH services, assign multiple external ports, such as 2222

and 2223

.

TCP Services Such as Databases, Redis, and MQTT

Databases, Redis, MQTT, and similar services are also TCP services, and their configuration is similar to SSH. However, they generally should not be directly exposed to the public internet; it is more appropriate to access them via the internal network, a VPN, a jump server, or Cloudflare Tunnel.

If you do need to temporarily expose PostgreSQL, the configuration would look something like this:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: 强密码
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.tcp.routers.postgres.rule=HostSNI(`*`)'
      - 'traefik.tcp.routers.postgres.entrypoints=postgres'
      - 'traefik.tcp.routers.postgres.service=postgres'
      - 'traefik.tcp.services.postgres.loadbalancer.server.port=5432'

networks:
  traefik:
    external: true

. The Traefik main configuration needs to include:

command:
  - '--entrypoints.postgres.address=:5432'
ports:
  - '5432:5432'

[!danger] Exposing a database directly to the public internet carries high risks. At a minimum, restrict the source IPs in the security group, use strong passwords, enable TLS for the database itself, and prioritize access via VPN or the internal network.

Common Operations Commands

View Traefik logs:

docker logs -f traefik

Check Traefik version:

docker exec traefik traefik version

Restart Traefik:

cd /opt/traefik
docker compose restart traefik

Update Traefik:

cd /opt/traefik
docker compose pull
docker compose up -d

It is recommended to back up certificate files before updating:

cp /opt/traefik/letsencrypt/acme.json /opt/traefik/letsencrypt/acme.json.bak

Check the final Compose configuration:

cd /opt/traefik
docker compose config

This command is very useful when troubleshooting whether the .env variable is taking effect.

Troubleshooting Common Issues

IssuePossible CauseResolution
Certificate issuance consistently failsThe domain name is not resolved to the server, port 80 is not open, or DNS changes have not taken effectCheck dig 域名 +short, security groups, and ufw status
404 error when accessing the domainContainer does not have traefik.enable=true configured, or the Host rule is incorrectCheck container labels and Routers in the Traefik Dashboard
502 error when accessing the domainTraefik cannot find the application container port or the network is unreachableCheck loadbalancer.server.port and whether the container is added to the traefik network
Dashboard is inaccessibleThe Dashboard domain is not resolved, Basic Auth is configured incorrectly, or the route label is incorrectCheck docker logs -f traefik
HTTP Not Redirecting to HTTPSMissing entrypoint redirection configurationCheck entrypoints.web.http.redirections.entrypoint.*
Let's Encrypt Rate LimitingFrequent retries after repeated failuresTroubleshoot the configuration before restarting; use the ACME staging server during debugging
Cloudflare DNS-01 failureInsufficient API token permissions or incorrect zone scopeVerify that the token has Zone:Read and DNS:Edit, and is bound to the correct domain
Access Issues After Enabling Cloudflare Orange CloudSSL/TLS mode is not set to “Full strict,” or the origin server certificate has not been issuedConfigure Cloudflare SSL/TLS -> Full (strict) and check the Traefik certificate logs
.env variable not taking effectFile is not in the same directory as Compose, or variable name is incorrectExecute docker compose config in /opt/traefik
WebSocket connection failedIncorrect backend path, server not handling "Upgrade," or Cloudflare rules affecting the connectionFirst, bypass Cloudflare to test; then check application logs and routing rules
File upload failedBackend restrictions, Cloudflare upload limits, Traefik buffering limitsCheck the application configuration, Cloudflare quota limits, and maxRequestBodyBytes separately
Unable to connect via SSHTCP entrypoint not open, host port conflict, incorrect TCP service portCheck ports, security groups, ufw, and traefik.tcp.services.*.port
Multiple SSH services cannot be load-balanced by domain nameSSH lacks SNIConfigure different entrypoints and external ports for different SSH services

Minimum Security Checklist

After placing Traefik at the public network entry point, security configuration must be taken seriously. My minimum checklist is:

  • Do not enable --api.insecure=true for public network access.
  • Do not expose 8080 to the public network.
  • Mount Docker sockets as read-only: /var/run/docker.sock:/var/run/docker.sock:ro.
  • Disable automatic port exposure by default: --providers.docker.exposedbydefault=false.
  • The dashboard must use HTTPS and Basic Auth.
  • Set permissions for .env to 600; do not expose the Cloudflare API token.
  • Grant the Cloudflare API token access only to necessary domains and with minimal permissions.
  • Use fixed-version images, such as traefik:v3.6; do not use latest long-term.
  • Prioritize adding business containers only to the Traefik network; do not directly expose the host machine’s ports.
  • For TCP services such as SSH, databases, and MQTT, assess individually whether public exposure is truly necessary.
  • In scenarios where VPNs, internal networks, or Cloudflare Tunnel can be used, do not directly expose database ports to the public internet.

Summary

Traefik is best suited for solving the problem of “increasingly difficult-to-maintain ingress configurations as the number of services on a cloud server grows.” It separates reverse proxy rules from a centralized configuration file and places them on each container’s own labels, keeping the service deployment declaration and routing declaration together.

Once this setup is working, adding new services typically requires only four steps:

  1. Add DNS resolution for the domain name.
  2. Add the container to the traefik network.
  3. Declare the Host, entrypoint, certificate resolver, and internal port in the container labels.
  4. docker compose up -d Start the service.

From a maintenance perspective, this approach is better suited for personal servers, small-team servers, and multi-service experimental environments than manually writing an Nginx server block every time. With clear entry points, automatic certificate renewal, and a unified integration method, you won’t have to start from scratch when later expanding to WebSocket, gRPC, SSH, or TCP services.

References

Keep reading