Traefik: managing both HTTP and HTTPS connections separately

Date

Tags
#selfhosting

Introduction

If you have used the wonderful Traefik before to route all your traffic to different docker containers or other services, you'll know how easy it is to add SSL certificates and secure HTTPS connections, simple and free thanks to their integration with Let's Encrypt.

You will first need the following lines in the traefik.toml file:

[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http]
      [entryPoints.web.http.redirections]
        [entryPoints.web.http.redirections.entryPoint]
          to = "websecure"
          scheme = "https"

  [entryPoints.websecure]
    address = ":443"

To learn more about this setup using traefik.yml or the CLI, please check out the documentation.

What these lines do is make sure that Traefik listens to the correct ports, 80 for HTTP connections (or web) and 443 for HTTPS connections (or websecure). Because we only want secure connections coming from the internet, Traefik is instructed to always redirect web entrypoint connections to the websecure entrypoint. Easy and safe!

Now, let us get a SSL certificate and secure those HTTPS connections:

[certificatesResolvers.myresolver.acme]
  email = "test@example.com"
  storage = "acme.json"
  [certificatesResolvers.myresolver.acme.httpChallenge]
    entryPoint = "web"

Setting up the certificateResolver is only half the process, now you need to make sure your docker container uses it by appending a few labels to docker-compose.yml:

mycontainer:
  image: someimage
  labels:
    - traefik.http.routers.router0.rule=Host(`example.com`)
    - traefik.http.routers.router0.tls=true
    - traefik.http.routers.router0.tls.certresolver=myresolver

To learn more about this setup using swarm, kubernetes or rancher, please check out the documentation.

You don't often get so much functionality out of a few lines of code: the service provided by mycontainer, whether it's a Nextcloud container, a Wallabag container or any other service you might enjoy using, is now available by visiting example.com with HTTPS enabled automatically ensuring a secure connection now and in the future as Traefik manages the certificate renewal for you.

Once you start hosting 20, 30, maybe 40 containers and making them accessible to yourself, to friends and family or the entire world through the internet, Traefik really starts to make your self-hosting life a whole lot more enjoyable.

The problem: home connections

There's one use-case where the "HTTPS everything" credo becomes counter-productive: services that should only be accessible via the home network.

In my home, I monitor a lot of services and devices using Telegraf which sends the metrics to an InfluxDB database to be analyzed by Grafana, a setup also known as the TIG stack. All these services are hosted on a server running inside my home.

The thing is, I don't need access to my Grafana dashboards outside my home. In fact, I don't even want those dashboards and the data they display available on the internet. So, I can just make up a domain that doesn't exist, say "grafana.lan", ensure that my home's DNS resolver (this could be your router or, in my case, PiHole) sends any grafana.lan requests to my server instead of to the internet and just make my Grafana container respond to grafana.lan requests using Traefik labels:

grafana:
  image: grafana/grafana
  labels:
    - traefik.http.routers.grafana.rule=Host(`grafana.lan`)
    - traefik.http.services.grafana.loadbalancer.server.port=3000

I added a server.port label to make sure Traefik forwards all requests to the correct port that Grafana listens to, in this case 3000.

I removed the TLS-related labels since I won't be needing HTTPS! Inside my home, I can just visit http://grafana.lan since I won't be using the dangerous internet connections outside my home.

Simple, right?

Well, no. We have a problem, because this setup won't work.

Redirect HTTP to HTTPS

Remember the first lines we added to traefik.toml? The ones that redirected all HTTP requests to the HTTPS entrypoint? Well, every time we want to visit http://grafana.lan, it will be redirected to https://grafana.lan. In the best case scenario, your browser will display a few flashy warnings that you are about to enter a website over an unsecured connection, with a hidden button that allows you to proceed anyway. In the worst case scenario, your browser will not allow you anywhere close to your own container because there is a certificate problem.

So, the problem is that there is no certificate for grafana.lan? Well, simple! Let's just add back the TLS labels and have a secure connection within the home!

Again, no can do. A domain needs to exist and be accessible from the internet in order for a SSL certificate to be issued. Since neither criteria are applicable to our grafana.lan, we can't get a SSL certificate.

Luckily, there is a solution.

The solution: domain-specific HTTP redirection

What we need to do is instruct Traefik to not redirect all HTTP connections to HTTPS, but only those that we can access from the internet. The connections that stay in the house should remain HTTP connections.

Remember these lines?

[entryPoints.web.http]
  [entryPoints.web.http.redirections]
    [entryPoints.web.http.redirections.entryPoint]
      to = "websecure"
      scheme = "https"

These need to be removed from traefik.toml so that you keep the basic entrypoint definitions:

[entryPoints]
  [entryPoints.web]
    address = ":80"

  [entryPoints.websecure]
    address = ":443"

Next, we need to add a so-called dynamic file configuration. Add the following line to the [providers] section in traefik.toml:

[providers.file]
  filename = "/path/to/traefik_dynamic.toml"
  watch = true

Create the file /path/to/traefik_dynamic.toml and add the following content:

[http]
  [http.middlewares]
    [http.middlewares.redirect_to_https]
      [http.middlewares.redirect_to_https.redirectscheme]
        scheme = "https"

  [http.routers]
    [http.routers.web_redir]
      rule = "HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`)"
      entryPoints = ["web"]
      middlewares = ["redirect_to_https"]
      service = "api@internal"

This code instructs Traefik to add a new middleware whose only responsibility is to redirect HTTP requests to HTTPS requests.

The next few lines applies this middleware to a router. The trick here is that this router is only applied to requests that target example.com and all its subdomains. You could add as many domains as you need to get them to always redirect to HTTPS. Our home domain grafana.lan is not in the list, therefore the middleware will not be applied to it and the connection will remain HTTP!

Note: the router points to the service api@internal. This is done because a router always needs a service even when, as in this case, the service will never be used because the connection is redirected to HTTPS and after that, this router is no longer used. If you don't like using api@internal for this purpose, you could always run a harmless whoami container and point the router to that service.

Conclusion

This Traefik setup takes a bit more effort but allows you to gracefully handle two different types of connections.

Connections coming from the internet are secured via SSL certificates and will always happen over HTTPS.

Connections coming from within the house stay within the house, they happen over HTTP and you can feel a lot safer knowing the internet no longer has direct access to that container.