Traefik vs Nginx (Proxy Manager): Choosing the right tool for container routing

Introduction

In the self-hosted community, many tend towards setups involving NGINX or NGINX Proxy Manager to route to their containers. This article contrasts Nginx Proxy Manager and Traefik to elucidate why the latter might be the optimal choice for most containerized environments.

The downsides of NGINX Proxy Manager

Many appreciate that NGINX Proxy Manager features a user-friendly web interface, eliminating the need to manually configure nginx. Yet this convenience comes at a cost: it supports a limited set of configurations and features. For instance, a PR to add support for the PROXY Protocol has remained unattended for quite a while: GitHub PR Link.

Internally, Nginx Proxy Manager leverages templates to spawn Nginx configurations. Though this offers some kind of flexibility, it introduces another layer of abstraction, potentially resulting in more intricate or inconsistent configurations, especially when custom modifications are applied.

This challenge isn't unique to Nginx Proxy Manager. Within the Kubernetes sphere, when juxtaposing Traefik with ingress-nginx, Traefik's API-native nature gives it an edge. Being API-native, Traefik inherently understands and aligns with the language of your platform. It effortlessly melds with your orchestration system, streamlining configurations.

Routing with Traefik

Do we genuinely need a separate web interface? Traefik effortlessly communicates in the language of containers. Imagine creating a container, adding a label, and saying, "Route this domain to my container." With Traefik, it's one additional line in your compose.

Example:

services:
  mycontainer:
    image: traefik/whoami # the whoami container is simply a hello world container showing the IP
    labels:
      - traefik.enable=true
      - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`)

With just a label for a container, whether it's via Docker, Docker Compose, or Portainer, Traefik identifies it and immediately routes accordingly. What's more, there's no need to specify a port; Traefik intelligently searches for one within your container. But that's just a glimpse of its capabilities. Traefik supports countless middlewares for request modifications, header additions, authentication, and more.

For instance, to secure your site with Basic Authentication:

 labels:
      - traefik.enable=true
      - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`)
      # Define Middleware, name it test-auth
      - "traefik.http.middlewares.test-auth.basicauth.users=test:passwordINhtpasswordformat"
      # Now add middleware to our router
      - traefik.http.routers.mycontainer.middlewares=test-auth

Deploying Traefik itself

Every application has its docker-compose file (or Portainer Stack), with Traefik having its own compose file/Stack. Below is a representative example:

version: '3'

services:
  reverse-proxy:
    image: traefik:v2.9
    restart: always
    command:
      - --entryPoints.http.address=:80 
      - --providers.docker 
      - --providers.docker.exposedByDefault=false 
      - --providers.file.directory=/opt/traefik
    ports:
      # The HTTP port
      - "80:80"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
      # For additional traefik configuration files if needed
      - /opt/traefik:/opt/traefik
    network_mode: host

In this configuration, Traefik has an entrypoint set for port 80. Additionally, I've mounted the Docker socket, enabling Traefik to read labels from other containers. To simplify routing, I've set the stack to network_mode: host. This allows Traefik to route to other containers without the need to include them in a separate Docker network.

For those interested in adding TLS support via LetsEncrypt, the process is straightforward. Once you've set up the configuration correctly to Traefik itself, You also just have to add another label to your container pointing to the name of the resolver. This action alone is sufficient for Traefik to automatically retrieve and apply the necessary certificate.

Using Traefik with the Authentik Middleware

Traefik is also a good choice when you want to use it in conjunction with Authentik for safeguarding websites using Single Sign-On (SSO).

Below is a configuration example that incorporates this setup. Note that the Traefik dashboard is enabled in this example, and it's further secured using Authentik.

version: '3'

services:
  reverse-proxy:
    image: traefik:v2.9
    restart: always
    command: 
      - --api.dashboard=true 
      - --entryPoints.http.address=:80 
      # If you have another reverse proxy in front doing tls termination
      # - --entryPoints.http.forwardedHeaders.insecure 
      # - --entryPoints.http.proxyProtocol.trustedIPs=10.10.10.101
      - --entryPoints.traefik.address=:9401 
      - --providers.docker 
      - --providers.docker.exposedByDefault=false 
      - --providers.file.directory=/opt/traefik
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "9401:9401"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/traefik:/opt/traefik
    network_mode: host
    labels:
      - traefik.enable=true
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=authentik@docker"
      - "traefik.http.services.dashboard.loadbalancer.server.port=9401"
  authentik-proxy:
    image: ghcr.io/goauthentik/proxy
    ports:
        - 9000:9000
    environment:
        AUTHENTIK_HOST: https://auth.example.com # your authentik url
        AUTHENTIK_INSECURE: "false"
        AUTHENTIK_TOKEN: THETOKENWHICHAUTHENTIKGIVES
    labels:
        traefik.enable: true
        traefik.port: 9000
        traefik.http.routers.authentik.rule: PathPrefix(`/outpost.goauthentik.io/`)
        # `authentik-proxy` refers to the service name in the compose file.
        traefik.http.middlewares.authentik.forwardauth.address: http://127.0.0.1:9000/outpost.goauthentik.io/auth/traefik
        traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
        traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
    restart: unless-stopped

With the above setup in place, you can protect any application by simply associating it with the Authentik middleware in another stack:

services:
  mycontainer:
    image: traefik/whoami # the whoami container is simply a hello world container showing the IP
    labels:
      - traefik.enable=true
      - traefik.http.routers.mycontainer.rule=Host(`mycontainer.myapp.cc`)
      - traefik.http.routers.mycontainer.middlewares=authentik@docker

Routing traffic to Virtual Machines

When it comes to routing traffic to VMs with Traefik, labels aren't an option. However, the dynamic configuration, managed through files, offers an elegant solution. In the configuration above, I mounted /opt/traefik within the Traefik container. To set up routing to a VM, place a YAML configuration file in that directory:

http:
  routers:
    my-vm-router:
      rule: "Host(`vm.example.com`)"
      service: "vm-service"
      middlewares: []
      entryPoints:
        - "http"

  services:
    vm-service:
      loadBalancer:
        servers:
          - url: "http://VM_IP_ADDRESS:VM_PORT"

Traefik dynamic configuration (e.g. /opt/traefik/examplevm.yml)

Cool thing is, there's no need to restart it after modifying the configuration. Just place or edit the configuration, and Traefik will refresh itself automatically.

If there are errors, they will be shown in the Traefik logs (or logs of the traefik container). Also you can check traefiks dashboard if your setup works as intended.

One more handy feature to keep in mind: Traefik also supports the routing of TCP and UDP traffic!

The Strengths of Nginx: Advanced Caching and Beyond

While Traefik excels in containerized environments, one cannot downplay Nginx's prowess. Renowned for its ability to efficiently serve static content, Nginx comes equipped with sophisticated caching mechanisms.

Nginx offers capabilities that Traefik currently doesn't:

  • Directly serve static files
  • Do advanced caching (like cache streaming video files)

While a Traefik middleware could, in theory, offer these services, none exists for such purposes as of now.

  • Modify or inject HTML at multiple points using Regex
  • Secure Links
  • Google PageSpeed Module

However, it's worth noting that with NGINX Proxy Manager, the full suite of NGINX's advanced features mentioned above remains out of reach.

Conclusion

Tools like NGINX Proxy Manager filled a gap before the advent of comprehensive reverse proxies like Traefik. But in the present landscape, it seems to amalgamate the limitations of both Nginx and Traefik: restricted features paired with manual configurations. While Nginx itself offers a broad feature set with manual configurations, Traefik provides a somewhat limited feature set with the beauty of automatic configurations. It's thus reasonable to advocate for a choice between raw Nginx or API-native proxies like Traefik.