cloud hosting

Nov 01, 2024

27 min read

Using Traefik as a Reverse Proxy for Multiple Hosts with Docker Compose

Written by

Abdelhadi Dyouri

This guide will cover how to use Traefik as a reverse proxy to manage multiple hosts with Docker Compose.

Suppose you had deployed several microservices using Docker and you want to connect these microservices to the outside world. You will probably use some reverse proxy solution that will act as a frontend for these microservices. Nginx is the most popular and efficient reverse proxy solution, but it was written before container technology (like Docker) became so popular. The modern solution for hosting multiple hosts with Docker is now Traefik.

What is Traefik?

Traefik is a modern reverse proxy solution written in GO to meet modern containerized challenges.

Unlike a traditional reverse proxy, which requires manual configuration, Traefik uses service discovery to dynamically configure routing. Traefik supports all major protocols, leveraging a rich set of middleware for load balancing, rate-limiting, circuit-breakers, mirroring, authentication, and more. Traefik also supports SSL termination and works with ACME providers (like Let’s Encrypt) for automatic certificate generation. Source.

Traefik was developed with containerized technology in mind, which means there’s a lot of new features available in comparison with other reverse proxy solutions. When running, Traefik does the following and more:

  • Dynamically updates the configuration and loads it without a restart.
  • Load balances requests from the outside world using multiple available algorithms.
  • Enables HTTPS for microservices using Let’s Encrypt.
  • Provides a clean web UI.
  • Supports for WebSocket, HTTP/2, GRPC.
  • Provides metrics to Rest, Prometheus, Datadog, Statsd, InfluxDB.

Traefik Reverse Proxy for Multiple Hosts with Docker Compose

Why Use Traefik as a Reverse Proxy for Multiple Hosts?

Say, for example, you want to remove one of the microservices from the container or change its port, and, as a result, you have to edit the Nginx configuration file to reflect the new container settings. Now, consider another situation where you add, delete, or scale the microservices application several times in a day—now, you’re editing the routes and other settings several times in the reverse proxy configuration file.

In this article, we will check how to install Traefik and configure it in a standalone and Dockerized environment, and then give examples of connecting it to your microservices in the backend.

Prerequisites to Install Traefik

  • A VPS running Ubuntu 24.04. Get your Linux VPS hosting from a reputable and trustworthy provider like SSD Nodes. We offer powerful Ubuntu servers and the best deals. Take a look at our offerings and prepare for your mind to be blown 🤯.
  • A non-root, sudo-enabled user. If you only have a root user, see our SSH tutorial for details on creating new users.
  • A working Docker installation—for information about how to install Docker, check out our getting started with Docker tutorial.

Using Traefik without Docker

Our first goal is to install Traefik  and use it to route a few applications (backend) using Traefik configuration without Dockerizing them. To do that, we’ll install Apache and Nginx and configure them to use different ports, as they will act as a backend.

First, update your system package index:

$ sudo apt update

Install Apache:

$ sudo apt install apache2

Change the port number to 8083 from 80 in /etc/apache2/ports.conf and restart Apache.

$ sudo nano /etc/apache2/ports.conf
$ sudo systemctl restart apache2

Next, install Nginx:

$ sudo apt install nginx

Create a custom welcome page for the Nginx server to distinguish between the two servers:

$ sudo mkdir /var/www/nginx_html
$ sudo echo '<!DOCTYPE html><html><body><h1>Welcome to My Nginx Page</h1></body></html>' | sudo tee /var/www/nginx_html/index.html

Next, edit the default Nginx configuration file:

$ sudo nano /etc/nginx/sites-available/default 

Point Nginx to the new /var/www/nginx_html directory, and the 8082 port number:

server {
        listen 8082 default_server;
        listen [::]:8082 default_server;
        # ...
        # ...
        # ...
        
        root /var/www/nginx_html;
        # ...
        # ...
        # ...
        }

Next, restart Nginx:

$ sudo systemctl restart nginx

Now both Apache and Nginx will be available in the port number 8083 and 8082, respectively.

Step 1: Install Traefik

To install Traefik as a standalone environment, we need to download the binary and edit the Traefik configuration file and rules files.
Download the Traefik binary from the releases page:

$ wget https://github.com/traefik/traefik/releases/download/v3.1.4/traefik_v3.1.4_linux_amd64.tar.gz

Remember to replace v3.1.4 with the latest release, or another binary that is compatible with your system.

Check the integrity of the downloaded file and compare it to the value in the traefik_v3.1.4_checksums.txt file:

$ sha256sum ./traefik_v3.1.4_linux_amd64.tar.gz

Next, extract the compressed file:

$ tar -zxvf traefik_v3.1.4_linux_amd64.tar.gz

You can now use ./traefik to run Traefik configurations.

Step 2: Traefik Configuration

To host multiple websites with Traefik, you’ll need to create a configuration file that routes each domain to the corresponding port.

First, we’ll create a traefik.yml file that gets automatically detected by Traefik:

log:
  level: "DEBUG"

entryPoints:
  web:
   address: ":80"

providers:
  file:
    filename: "./rules.yml"
    watch: true

In this traefik.yml file, you have the following:

  • log: Sets log level to DEBUG for detailed output.
  • entryPoints: Defines web entry point to listen on port 80 for HTTP traffic.
  • providers: This tells Traefik to load dynamic configuration (such as routes and services) from the rules.yml file, and watch for changes.

Step 3: Set up Traefik Rules

Next, create a rules.yml file to define dynamic routing rules:

http:
  routers:
    router1:
      rule: "Host(`nginx.example.com`)"
      service: service1
      entryPoints:
        - web
    router2:
      rule: "Host(`apache.example.com`)"
      service: service2
      entryPoints:
        - web

  services:
    service1:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8082"
    service2:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8083"

Here, you definerouters to define traffic rules and route them to services:

  • router1: Routes traffic requests for nginx.example.com to service1.
  • router2: Routes traffic requests for apache.example.com to service2.

The services are defined as follows:

  • service1: Forwards traffic to the Nginx server at http://127.0.0.1:8082.
  • service2: Forwards traffic to Apache server at http://127.0.0.1:8083.

Both routers listen on the web entry point, which was defined in the traefik.yml file.

Make sure you are in the Traefik directory that hosts both the Traefik binary and the traefik.yml file, then run the following command:

$ ./traefik

Now, if you navigate to your domain names, you should see two different welcome pages, one for Nginx and one for Apache:

http://nginx.example.com/
http://apache.example.com/

With this, you now have two websites on one server with Traefik as a proxy. However, these two websites are served through HTTP and are not secured with HTTPS. We’ll fix this in the next section and add a Let’s Encrypt certificate to both domains.

Step 4: Using TLS in Traefik with Let’s Encrypt

To use Let’s Encrypt with your Traefik configuration, edit traefik.yml by adding a websecure entry point that listens on port 443 , which is the default HTTPS port, and a certificatesResolvers section to define how the Let’s Encrypt certificate will be resolved:

log:
  level: "DEBUG"

entryPoints:
  web:
   address: ":80"
  websecure:
   address: ":443"

providers:
  file:
    filename: "./rules.yml"
    watch: true

certificatesResolvers:
  myresolver:
    acme:
      email: "[email protected]"
      storage: "acme.json"
      httpChallenge:
        entryPoint: web

providers:
  file:
    filename: "./rules.yml"
    watch: true

Next, update rules.yml by adding the websecure entry point and a tls option that uses the myresolver certificate resolver you defined in the traefik.yml file earlier, in addition to a tlsoptions field that specifies the TLS version.

http:
  routers:
    router1:
      rule: "Host(`app1.your_domain.com`)"
      service: service1
      entryPoints:
        - web
        - websecure
      tls:
        certResolver: myresolver
        options: tlsoptions

    router2:
      rule: "Host(`app2.your_domain.com`)"
      service: service2
      entryPoints:
        - web
        - websecure
      tls:
        certResolver: myresolver
        options: tlsoptions

  services:
    service1:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8082"
    service2:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8083"
tls:
  options:
    tlsoptions:
      minVersion: VersionTLS12

 

Now, you can start Traefik again—but remember to kill previous instances of it first.

$ ./traefik

With this, you are now hosting two different web services via HTTPS with Traefik. Next, you’ll learn how to use Traefik as a load balancer.

Step 5: Using Traefik as a Load Balancer

With little modification to the above rules file, we can make Traefik to act as a load balancer between both Apache and Nginx instances.

$ nano rules.yml

You can replace the file’s contents with the following:

http:
  routers:
    router1:
      rule: "Host(`your_domain.com`)"
      service: service1
      entryPoints:
        - web
        - websecure
      tls:
        certResolver: myresolver
        options: tlsoptions

  services:
    service1:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8082"
            weight: 2
          - url: "http://127.0.0.1:8083"
            weight: 1

tls:
  options:
    tlsoptions:
      minVersion: VersionTLS12

Weight determines the proportion of traffic each server will receive. In this example:

  • The Nignx server at http://127.0.0.1:8082 has a weight of 2.
  • The Apache server at http://127.0.0.1:8083 has a weight of 1.

This means that for every 3 requests, 2 will go to http://127.0.0.1:8082 and 1 will go to http://127.0.0.1:8083.

Now, you can start Traefik again—but remember you have to kill previous instances of it first.

$ ./traefik

Use your browser to visit the domain name that you entered into the rules.yml file and hit refresh. You’ll find that Traefik is both proxying load balancing the Apache and Nginx backends.

Now, let’s look at this same process, but using Docker, docker-compose, and containerized/Dockerized microservices, which is likely how many of you will end up using Traefik.

Using Traefik with Docker Compose

Step 1: Traefik Reverse Proxy Example for Docker

In this section, we'll explore an example of a Traefik reverse proxy with Docker compose.

First, let’s create a docker-compose.yml file, in which we will define services for Traefik and the other two microservices—Apache and Nginx, just like before.

$ nano docker-compose.yml

Add the following:

version: '3'
services:
  reverse-proxy:
    image: traefik:latest
    command: --configFile=/etc/traefik/traefik.yml
    ports:
      - "80:80"       # HTTP
      - "443:443"     # HTTPS
      - "8080:8080"   # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yml:/etc/traefik/traefik.yml
      - ./acme.json:/acme.json
    networks:
      - traefik

  apache:
    image: httpd:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.apache.rule=Host(`apache.example.com`)"
      - "traefik.http.routers.apache.entrypoints=web,websecure"
      - "traefik.http.routers.apache.tls.certresolver=myresolver"
      - "traefik.http.services.apache.loadbalancer.server.port=80"
    networks:
      - traefik

  nginx:
    image: nginx:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`nginx.example.com`)"
      - "traefik.http.routers.nginx.entrypoints=web,websecure"
      - "traefik.http.routers.nginx.tls.certresolver=myresolver"
      - "traefik.http.services.nginx.loadbalancer.server.port=80"
    networks:
      - traefik

networks:
  traefik:
    external: true

Three services have been defined in the above docker-compose.yml file, and they are reverse-proxy, apache and nginx.

In the port section, 8080 is the port used by Traefik for its web interface and port 80is used for all default HTTP requests. In the volume section, the Docker socket is accessed by Traefik to route requests to the right container.

We need to knit all the three services together so that they can communicate with each other, which is why we created the traefik network at the end of the file.

As for the services, we have the following:

  • reverse-proxy service: This is the Traefik service acting as a reverse proxy, with ports for HTTP, HTTPS, and the Traefik dashboard.
  • Apache and Nginx services: Both have labels that define routing rules for Traefik:
    • traefik.http.routers.[service].rule: Routes traffic based on the Host.
    • traefik.http.routers.[service].entrypoints: Defines which entry points to use (web for HTTP, websecure for HTTPS).
    • traefik.http.routers.[service].tls.certresolver: Configures TLS with the myresolver certificate resolver, which we’ll define in the traefik.yml file next section.
    • traefik.http.services.[service].loadbalancer.server.port: Defines the internal port for each service.

Step 2: Run Traefik using Docker

First, create a new traefik.yml file. This is the configuration file referenced inside docker-compose.yml in the previous section. This Traefik configuration file defines the entry points, certificate resolver, and sets the provider as Docker:

log:
  level: "DEBUG"  # Set log level to DEBUG for detailed logs

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  traefik:
    address: ":8080"  # Dashboard entry point

certificatesResolvers:
  myresolver:
    acme:
      email: [email protected]
      storage: acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false
#api:
#  dashboard: true  # Enable the Traefik dashboard
#  insecure: true   # Allow access to the dashboard without authentication (for development)

Here, you have the following:

  • EntryPoints: Defines the ports that Traefik will listen to for HTTP (:80) and HTTPS (:443) traffic.
  • certificatesResolvers: This uses the ACME protocol to automatically generate SSL certificates for the domains (replace [email protected] with your real email). The great thing about Traefik is that these certificates are dynamic, meaning that if you add a new domain or subdomain to docker-compose.yml, Traefik will automatically fetch the key/certificate and store them in acme.json.
    • The httpChallenge is configured to solve TLS challenges over HTTP (port 80).
  • providers.docker: Enables Docker as the provider for dynamic routing, but exposedByDefault: false ensures that only services explicitly enabled via labels will be exposed by Traefik.

Make sure Traefik has access to your acme.json file, which stores certificate information:

$ touch acme.json
$ chmod 600 acme.json

Create the traefik network:

$ docker network create traefik

Start the services with Docker Compose:

$ docker compose up -d

You should see the following output:

[+] Running 19/19
 ✔ reverse-proxy Pulled                                                                             19.4s
   ✔ 43c4264eed91 Pull complete                                                                      3.0s
   ✔ eee3b43fedbd Pull complete                                                                      3.5s
   ✔ e951b536ebf8 Pull complete                                                                      5.9s
   ✔ 188dd7af1135 Pull complete                                                                      3.9s
 ✔ apache Pulled                                                                                    16.2s
   ✔ 62dd86107c65 Pull complete                                                                      1.3s
   ✔ 4f4fb700ef54 Pull complete                                                                      3.8s
   ✔ 509789394c2a Pull complete                                                                      3.8s
 ✔ nginx Pulled                                                                                     18.7s
   ✔ a2318d6c47ec Pull complete                                                                      0.8s
   ✔ 095d327c79ae Pull complete                                                                      1.5s
   ✔ 24b3fdc4d1e3 Pull complete                                                                      2.8s
   ✔ 3122471704d5 Pull complete                                                                      3.2s

With this, you’ll have two HTTPS-enabled applications served by Traefik at https://apache.your_domain.com/ and https://nginx.your_domain.com/

Step 3: Enable the Traefik Dashboard

If you want to access the Traefik dashboard, uncomment the last two lines in the traefik.yml file:

log:
  level: "DEBUG"

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  traefik:
    address: ":8080"  # Dashboard entry point

certificatesResolvers:
  myresolver:
    acme:
      email: [email protected]
      storage: acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false

api:
  dashboard: true  # Enable the Traefik dashboard
  insecure: true   # Allow access to the dashboard without authentication (for development)

Note: Allowing access to the Traefik dashboard without authentication poses a security risk, as it can expose sensitive configuration details to unauthorized users. Remember to only use this insecure option in testing environments.

You can then access the Traefik dashboard on port 8080 :

http://your_domain.com:8080/dashboard

In this dashboard, you can monitor and manage Traefik's routing, view active services, routers, middlewares, and review logs for troubleshooting.

Step 4: Access Traefik Logs

If you still can't access the dashboard, check the logs for errors:

$ docker compose logs reverse-proxy

Step 5: Set up Traefik to Redirect HTTP to HTTPS

To redirect HTTP to HTTPS with Traefik, you need to add a middleware for HTTP to HTTPS redirection and apply it to the web entrypoint. Open traefik.yml and add the following highlighted parts:

log:
  level: "DEBUG"  # Set log level to DEBUG for detailed logs
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
  traefik:
    address: ":8080"  # Dashboard entry point
certificatesResolvers:
  myresolver:
    acme:
      email: [email protected]
      storage: acme.json
      httpChallenge:
        entryPoint: web
providers:
  docker:
    exposedByDefault: false

With these changes, all HTTP traffic will be automatically redirected to HTTPS. Here's a breakdown of what happens:

  1. When a user accesses your site via HTTP (port 80), they hit the web entrypoint.
  2. The web entrypoint is configured to redirect all traffic to the websecure entrypoint using HTTPS.
  3. The websecure entrypoint handles the HTTPS traffic on port 443.

Your services (apache and nginx) are already configured to use both web and websecure entrypoints, so they will work correctly with this setup.

Remember to restart your Traefik container after making these changes:

$ docker-compose down
$ docker-compose up -d

This Traefik configuration will ensure that all HTTP traffic is redirected to HTTPS, improving the security of your web services.

Step 6: Using Traefik’s PathPrefix

Traefik's PathPrefix router rule feature allows you to route traffic based on URL paths. This is particularly useful when you want to direct requests to different services based on the URL path.

For example, you can set up requests to the /app1 URL to be served by Apache, and /app2 to be served by Nginx.

Here's an example of how to use PathPrefix with Traefik:

version: '3'
services:
  reverse-proxy:
    image: traefik:latest
    command: --configFile=/etc/traefik/traefik.yml
    ports:
      - "80:80"       # HTTP
      - "443:443"     # HTTPS
      - "8080:8080"   # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yml:/etc/traefik/traefik.yml
      - ./acme.json:/acme.json
    networks:
      - traefik

  apache:
    image: httpd:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.apache.rule=Host(`example.com`) && PathPrefix(`/app1`)"
      - "traefik.http.routers.apache.entrypoints=web,websecure"
      - "traefik.http.routers.apache.tls.certresolver=myresolver"
      - "traefik.http.services.apache.loadbalancer.server.port=80"
    networks:
      - traefik

  nginx:
    image: nginx:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`example.com`) && PathPrefix(`/app2`)"
      - "traefik.http.routers.nginx.entrypoints=web,websecure"
      - "traefik.http.routers.nginx.tls.certresolver=myresolver"
      - "traefik.http.services.nginx.loadbalancer.server.port=80"
    networks:
      - traefik

networks:
  traefik:
    external: true

In this updated configuration:

  1. The Apache service will handle requests to https://example.com/app1 and any subpaths (e.g., https://example.com/app1/users, https://example.com/app1/products, etc.).
  2. The Nginx service will handle requests to https://example.com/app2 and any subpaths.

The key changes are in the traefik.http.routers.[service].rule labels:

  • For Apache: Host(`example.com) && PathPrefix(`/app1`)
  • For Nginx: Host(`example.com`) && PathPrefix(`/app2`)

These rules combine the Host matcher with the PathPrefix matcher using the && (AND) operator. This means the rule will only match if both conditions are true.

A few important notes:

  1. Make sure to replace example.com with your actual domain name.
  2. The PathPrefix rule will match any path that starts with the specified prefix. For example, /app1 will match /app1, /app1/, /app1/users, etc.
  3. If you want to strip the prefix before forwarding the request to your service, you can add a middleware:
    - "traefik.http.middlewares.strip-app1.stripprefix.prefixes=/app1"
    - "traefik.http.routers.apache.middlewares=strip-app1"
    

    This would remove /app1 from the path before sending the request to the Apache service.

  4. If your services are serving content from subdirectories (e.g., Apache serving from /usr/local/apache2/htdocs/app1), you might not need to strip the prefix.

Remember to restart your Docker containers after making these changes:

$ docker-compose down
$ docker-compose up -d

This configuration allows you to host multiple applications or services on the same domain, differentiated by their URL paths.

Conclusion

With this, you’re now up and running with a proxied, load-balanced, fully Dockerized reverse proxy that’s fully HTTPS-enabled.

Traefik successfully installed

You installed Traefik, so now what?

Now that you have installed Traefik and run it from both the binary and using docker-compose, you’re ready to add more microservices! If you add new Docker images in the docker-compose.yml, Traefik will recognize newly added backends immediately and route the request to it without any further configuration of Traefik or restarting it.

For more configuration options, see the Traefik documentation.

Now, you might be asking, “What should I install behind this amazing Traefik reverse proxy?”

Well, how about you start with self-hosting NextCloud?

The options are endless—have fun and get more from your VPS at the same time!

A note about tutorials: We encourage our users to try out tutorials, but they aren't fully supported by our team—we can't always provide support when things go wrong. Be sure to check which OS and version it was tested with before you proceed.

If you want a fully managed experience, with dedicated support for any application you might want to run, contact us for more information.

Leave a Reply