Skip Navigation

NPM vs Traefik?

There have been a few Reddit, Lemmy and Youtube posts over the past week or so about Nginx Proxy Manager and their shortfalls, mostly towards CVEs and other security issues.

The problem is that unlike Traefik, NGINX Proxy Manager is actually easy to use. And before you recommend Caddy, that also has no GUI.

What do you use, if you have stuff exposed to the outside?

39 comments
  • Traefik. It has a GUI that I can use to see things, and (depending on your setup) you define the routes and stuff as part of your container definitions, minimal extra work required, makes setup and teardown a breeze. It is also nice that you can use it in all sorts of places, I have used it as Kubernetes ingress and as the thing that routed traffic to a Nomad cluster.

    I went from Apache to Nginx (manually configured, including ACME) to Traefik over the course of the past ~10 years. I tried Caddy when I was making the switch to Traefik and found it very annoying to use, too much magic in the wrong places. I have never actually used NPM, as it doesn't seem useful for what I want...

    Anyway, with traefik you can write your services in docker compose like this, and traefik will just pick them up and do the right thing:

     undefined
        
    version: "3"
    services:
      foo-example-com:
        image: nginx:1.24-alpine
        volumes: ['./html:/usr/share/nginx/html:ro']
        labels:
          'traefik.http.routers.foo-example-com.rule': Host(`foo.example.com`)
        restart: unless-stopped
        networks:
          - traefik
    networks:
      traefik:
        name: traefik-expose-network
        external: true
    
      

    It will just work most of the time, though sometimes you'll have to specify 'traefik.http.services.foo-example-com.loadbalancer.server.port': whatever or other labels according to the traefik docs if you want specific behaviors or middleware or whatever.

    And your deployment of traefik would look something like this:

     undefined
        
    version: '3'
    services:
      traefik:
        image: traefik:v2
        command: >-
          --accesslog=true
          --api=true
          --api.dashboard=true
          --api.debug=true
          --certificatesresolvers.le.acme.dnschallenge.provider=provider
          --certificatesresolvers.le.acme.storage=acme.json
          [ ... other ACME stuff ... ]
          --entrypoints.http.address=:80
          --entrypoints.http.http.redirections.entrypoint.to=https
          --entrypoints.http.http.redirections.entrypoint.scheme=https
          --entrypoints.https.address=:443
          --entrypoints.https.http.tls.certresolver=le
          --entrypoints.https.http.tls.domains[0].main=example.com
          --entrypoints.https.http.tls.domains[0].sans=*.example.com
          --entrypoints.https.http.tls=true
          --global.checknewversion=false
          --global.sendanonymoususage=false
          --hub=false
          --log.level=DEBUG
          --pilot.dashboard=false
          --providers.docker=true
        environment:
          [ ... stuff for your ACME provider ... ]
        ports:
          # this assumes you just want to do simple port forwarding or something
          - 80:80
          - 443:443
          # - 8080:8080 uncomment if you want to hit port 8080 of this machine for the traefik gui
        working_dir: /data
        volumes:
          - ./persist:/data
          - /var/run/docker.sock:/var/run/docker.sock
        networks:
          - traefik
        restart: unless-stopped
    networks:
      traefik:
        name: traefik-expose-network
        external: true
    
      

    Note that you'd have to create the traefik-expose-network manually for this to work, as that is how traefik will talk to your different services. You can get even fancier and set it up to expose your sites by default and auto-detect what to call them based on container name and stuff, but that is beyond the scope of a comment like this.

    Technically my setup is a little more complex to allow for services on many different machines (so I don't use the built-in docker provider), and to route everything from the internet using frp using proxy protocol so I don't expose my home IP... I think this illustrates the point well regardless.

    • Well, it works just fine for Docker containers, but trying to point it at other services is what is making my head hurt. With NPM it is literally "this IP at this port with this certificate = https://service.domain.tld"

       docker-compose.yml
          
      
      version: "3.3"
      
      services:
        traefik:
          image: "traefik:latest"
          container_name: "traefik"
          networks:
            - npm_bridge
          command:
            #- "--log.level=DEBUG"
            - "--providers.docker.exposedbydefault=false"
          ports:
            - "443:443"
            - "80:80"
            - "8180:8080"
          volumes:
            - "/docker/containers/traefik/letsencrypt:/letsencrypt"
            - "/docker/containers/traefik/:/etc/traefik"
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
      networks:
        npm_bridge:
          external: true
      
      
      traefik.toml
      
      [entryPoints]
        [entryPoints.web]
          address = ":80"
          #[entryPoints.web.http.redirections.entryPoint]
            #to = "websecure"
            #scheme = "https"
      
        [entryPoints.websecure]
          address = ":443"
      
      [api]
        dashboard = true
        insecure = true
      
      [certificatesResolvers.letsencrypt.acme]
        email = "[redacted]"
        storage = "/letsencrypt/acme.json"
        #caserver = "https://acme-staging-v02.api.letsencrypt.org/directory"
        caserver = "https://acme-v02.api.letsencrypt.org/directory"
        [certificatesResolvers.letsencrypt.acme.tlsChallenge]
      
      [providers]
        [providers.docker]
          watch = true
          network = "npm_bridge"
        [providers.file]
          directory = "/etc/traefik/dynamic"
          watch = true
      
      traefik_dynamic.toml
      
      [tls.options]
        [tls.options.default]
          sniStrict = true
          minVersion = "VersionTLS12"
          curvePreferences = [
            "secp521r1",
            "secp384r1"
          ]
          cipherSuites = [
            "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
            "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
            "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
          ]
          [tls.options.mintls13]
            minVersion = "VersionTLS13"
      
      [http]
        [http.routers]
          [http.routers.jellyfin]
            rule = "Host(`jellyfin.[redacted].com`) && PathPrefix(`/`)"
            service = "jellyfin"
            entrypoints = "websecure"
              [http.routers.librespeed]
            rule = "Host(`librespeed.[redacted].com`) && PathPrefix(`/`)"
            service = "librespeed"
            entrypoints = "websecure"
      
        [http.services]
          [http.services.jellyfin.loadBalancer]
            [[http.services.jellyfin.loadBalancer.servers]]
              url = "http://10.0.1.201:8096"
          [http.services.librespeed.loadBalancer]
            [[http.services.librespeed.loadBalancer.servers]]
              url = "http://10.0.1.201:10016"
      
      
        

      This setup sadly ends up with ERR_SSL_UNRECOGNIZED_NAME_ALERT for both services. These URLs are NOT proxied through Cloudflare. I'm trying to move from Truecharts + Traefik into manual docker + traefik

      • Is traefik successfully getting the cert via LE? It sounds like for one reason or another it is still using the built-in/default cert for those services. You can check the traefik log's LEGO lines, and/or look at your /letsencrypt/acme.json.

        In my example I specified entrypoints.https.http.tls.domains, but I think that is only necessary when you're doing wildcard domains with a DNS solver.

        edit: You may need to use the file provider rather than trying to specify stuff in the main config toml... traefik differentiates from "static" config that it has to know at boot time and can't change and "dynamic" config like routers and stuff.

39 comments