How do I best configure many websites (static virtual hosts) on the same backend container?

I have a webserver (currently nginx, but could just as well be Apache) which hosts many static websites as virtual hosts. With “many”, I mean dozens, and I keep adding and removing them.

In my setup, I have a docker container with traefik, and a docker container with nginx. The same nginx container serves all these websites (that point is key to my question).

What is the best way to tell traefik about these host names, so that traefik can create Let’s Encrypt certificates for it, and route traffic to this container?

The standard way seems to be to use a label on the nginx container, e.g.

docker run ...
  -l traefik.backend=webserver \
  -l traefik.port=80 \
  -l traefik.frontend.rule="Host:example.com,www.example.com,docs.example.com,example.net,www.example.net,docs.example.net,example.org,www.example.org,example.de,www.example.de,development.com,www.development.com"

and so on. That list goes on and on and on. This works, but:

This is not very maintainable.

Worse, Traefik seems to pull one single cert for all these names. Let’s say development.com is a completely differ entity from example.com, and I don’t want both of them to be listed in the same cert.

Even worse, let’s say I made a mistake somewhere. I misconfigured docs.example.net. Or, worse, they all work, but then in the future, I forget to renew example.net. And my Let’s Encrypt cert needs to be renewed. Now, that renewal will fail, because if any one of the host names fails to verify, Let’s Encrypt will refuse the certificate, which is totally correct. But means that all my websites will be down, suddenly at any unforseable time in the future, if any of the hostnames has a problem. That’s a big risk. A risk one shouldn’t take. The websites should be independent in the certificate.

It appears I am not using this right. So, my question is: How can I better configure this, so that each website is independent (in the configuration of traefik, and esp. in the SSL certificate), but I still use only one webserver container for all of them?

Here’s what I tried:

  1. I tried to manually configure the certificates in [acme] sections:

    [[acme.domains]]
    main = “example.com
    sans = [ “www.example.com” ]
    [[acme.domains]]
    main = “example.org
    sans = [ “www.example.org” ]

That looks more sane to me than the long label line on docker run. traefik apparently tries to get these certs, and writes them to acme.json. But it doesn’t seem to use them. Even with these lines, traefik still uses the cert that has all the hostnames from the traefik.frontend.rule instead of the manually configured, more specific cert. That seems ill-advised.

Also, if I remove the hostname from the traefik.frontend.rule, traefik doesn’t find the backend and returns a 404 to the client. That’s logical, because traefik doesn’t know where to route the traffic for this host.

  1. I tried to set up [frontend] rules.

    [frontends]
    [frontends.example]
    backend = “webserver”
    [frontends.example.routes.com]
    rule = “Host:example.com,www.example.com,docs.example.com
    [frontends.example.routes.org]
    rule = “Host:example.org,www.example.org,docs.example.org

That seems to be the right direction, although the configuration directives are very chatty, esp. all the section headers.

But I couldn’t get this to work, all I got was “backend not found” in the traefik access log.

The last approach you tried should work.

“backend not found”

Perhaps you made a mistake in configuring your backend.

I just ran a quick test, and here is traefik.toml that worked for me:

logLevel = "DEBUG"

defaultEntryPoints = ["https","http"]

[api]
[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]

[file]

# rules
[backends]

  [backends.webserver]

    [backends.webserver.servers]
      [backends.webserver.servers.server0]
        url = "http://webserver" # put your nginx url here make sure it's reachable from inside of your traefik container

# Frontends
[frontends]
  [frontends.frontend1]
      backend = "webserver"
      passHostHeader = true
      [frontends.frontend1.routes.route0]
      rule = "Host:test1.domain.tld"

  [frontends.frontend2]
      backend = "webserver"
      passHostHeader = true
      [frontends.frontend2.routes.route0]
      rule = "Host:test2.domain.tld"

[acme]
email = "email@domain.tld"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
entryPoint = "https"
  [acme.dnsChallenge]
  provider = "digitalocean"
  delayBeforeCheck = 0

[[acme.domains]]
  main = "test1.domain.tld"
[[acme.domains]]
  main = "test2.domain.tld"