I love Traefik. So much so that if I can run one of my services through it, I do. Why? Mainly to gain the ability of easy certificate management (via Let’s Encrypt in my case) and not having to remember and forward obscure ports. But not every service I run needs to be exposed publicly..

For instance, I use code-server to write the majority of my code - including writing & managing these blog posts. And while code-server is protected by a password, it is using a basic auth mechanism that really shouldn’t be exposed to the internet. This is where Traefik’s IPWhiteList middleware comes in handy: I can quickly limit access to my code-server instance to IP’s or networks of my choosing.

IPWhiteList Overview

This diagram from the Traefik docs explains it well:

When a client makes a request, Traefik’s IPWhiteList middleware inspects the HTTP headers to determine if the request should be allowed to proceed (or not). This inspection can be based on a client’s remote address or X-Forwarded-For header information depending on how the middleware is configured. Official details in the docs, here.

How to Put it Together

We can use the trusty crccheck hello-world container to demonstrate (Note: I’m using Podman but should be the same for Docker). Here is a sample compose file we can work with:

version: 3.7


        image: docker.io/crccheck/hello-world
        hostname: hello.cthudson.com
        container_name: hello
          - "traefik.enable=true"
          - "traefik.http.routers.hello.rule=Host(`hello.cthudson.com`)"
          - "traefik.http.middlewares.hello-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.routers.hello.middlewares=hello-https-redirect"
          - "traefik.http.routers.hello-secure.entrypoints=websecure"
          - "traefik.http.routers.hello-secure.rule=Host(`hello.cthudson.com`)"
          - "traefik.http.routers.hello-secure.tls=true"
          - "traefik.http.routers.hello-secure.tls.certresolver=lets-encrypt"
          - "traefik.http.services.hello.loadbalancer.server.port=8000"
          - "traefik.http.middlewares.hello-ipwhitelist.ipwhitelist.sourcerange=,,"
          - "traefik.http.routers.hello-secure.middlewares=hello-ipwhitelist"

In the above compose file, we are running the crccheck hello-world image, using labels to enable Traefik and providing a few configuration options to serve the image how we would like to (as hello.cthudson.com, encrypted via Let’s Encrypt, forcing https, with the service within the container being on port 8000). For the purposes of this post, we will look at lines 19 & 20.

In line 19 we are providing our source ranges that will be granted access. In this example, we allow localhost (, the lan subnet (, and another subnet representing a VPN subnet ( If your inbound remote address does not satisfy one of these rules, you should be provided a 403 Forbidden error.

In line 20, we are attaching the “hello-ipwhitelist” IPWhiteList middleware to the “hello-secure” router. NOTE: Per the Traefik docs, whitelisting happens before the actual proxying to the backend takes place.

Here is a visual representation from the Traefik dashboard once the container was brought up with podman-compose:

Trafik Dashboard IPWhiteList Visual

Testing Time

Quite simple, really:

From network:

$ ip a | grep 192 && curl https://hello.cthudson.com
    inet brd scope global noprefixroute bond0
Hello World

                                       ##         .
                                 ## ## ##        ==
                              ## ## ## ## ##    ===
                           /""""""""""""""""\___/ ===
                      ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
                           \______ o          _,/
                            \      \       _,'

From outside allowed networks (tethering with my mobile in this case):

$ curl icanhazip.com && curl https://hello.cthudson.com