Multiple domains on one app

Hi

I'm using Traefik to manage several Laravel/Wordpress/... apps in a docker environment. Traefik is running in it's own docker container and is used to gain access to my various apps via HTTPS. All of which works fine - probably plenty of room for improvement if an expert were to look at it but I'm happy that it works.

Most apps work with only one domain, but one app in fact uses 8 different domains. So basically we have one default domain (let's say foo.local) which I currently have set up and which works, and also a list of "support" domains which are used in certain situations (e.g. bar.local, foo-status.local, bar-status.local,...). The app takes care of how all of these domains are handled, meaning which information is to be shown to the user etc. My problem is: I don't know how to set it up so that Traefik knows that my app can support all of these domains.

Below is my current docker-compose file for the app. When I do it like this, the default domain still works but I get a 404 nginx error page on the new domain. Aside from the file below, I also updated the laravel.conf file mentioned there to include all of the domains. All virtual hosts are identical except for the URL, for obvious reasons. They all point to the same root directory (app/public in my case, because Laravel wants it that way). If you'd like me to include that here, be sure to let me know. I doubt the error is in there though.

version: '3'


services:

  nginx:
    container_name: foo_nginx
    build: docker/nginx
    links:
      - php
    volumes:
      - ./:/app:cached
    restart: "unless-stopped"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.foo-secure.entrypoints=https"
      - "traefik.http.routers.foo-secure.rule=Host(`foo.local`)"
      - "traefik.http.routers.foo-secure.middlewares=foo-security"
      - "traefik.http.routers.foo-secure.tls.certresolver=myhttpchallenge"
      - "traefik.http.middlewares.foo-security.headers.browserXSSFilter=true"
      - "traefik.http.middlewares.foo-security.headers.customFrameOptionsValue=SAMEORIGIN"
      - "traefik.http.middlewares.foo-security.headers.forceSTSHeader=true"
      - "traefik.http.middlewares.foo-security.headers.frameDeny=true"
      - "traefik.http.middlewares.foo-security.headers.SSLHost=foo.local"
      - "traefik.http.middlewares.foo-security.headers.SSLRedirect=true"
      - "traefik.http.middlewares.foo-security.headers.STSIncludeSubdomains=true"
      - "traefik.http.middlewares.foo-security.headers.STSSeconds=63072000"
      - "traefik.http.routers.bar-status-secure.entrypoints=https"
      - "traefik.http.routers.bar-status-secure.rule=Host(`aanbieders-status.local`)"
      - "traefik.http.routers.bar-status-secure.middlewares=bar-status-security"
      - "traefik.http.routers.bar-status-secure.tls.certresolver=myhttpchallenge"
      - "traefik.http.middlewares.bar-status-security.headers.browserXSSFilter=true"
      - "traefik.http.middlewares.bar-status-security.headers.customFrameOptionsValue=SAMEORIGIN"
      - "traefik.http.middlewares.bar-status-security.headers.forceSTSHeader=true"
      - "traefik.http.middlewares.bar-status-security.headers.frameDeny=true"
      - "traefik.http.middlewares.bar-status-security.headers.SSLHost=bar-status.local"
      - "traefik.http.middlewares.bar-status-security.headers.SSLRedirect=true"
      - "traefik.http.middlewares.bar-status-security.headers.STSIncludeSubdomains=true"
      - "traefik.http.middlewares.bar-status-security.headers.STSSeconds=63072000"
    networks:
      - web
      - foo

  php:
    container_name: foo_php
    build: docker/php
    links:
      - db
    volumes:
      - ./:/app:cached
    working_dir: /app
    restart: "unless-stopped"
    networks:
      - web
      - foo

  db:
    container_name: foo_db
    image: mariadb
    command: --max_allowed_packet=67108864
    ports:
      - "34001:3306"
    volumes:
      - ./tests/_data/functional-dump.sql:/docker-entrypoint-initdb.d/data.sql:cached
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: foo
      MYSQL_USER: foo_user
      MYSQL_PASSWORD: foo_pwd
      DNSDOCK_ALIAS: mysql.foo.develop
    restart: "unless-stopped"
    networks:
      - web
      - foo

  testdb:
    container_name: foo_test_db
    image: mariadb
    command: --max_allowed_packet=67108864
    ports:
      - "34002:3306"
    volumes:
      - ./tests/_data/functional-dump.sql:/docker-entrypoint-initdb.d/data.sql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: foo
      MYSQL_USER: foo_user
      MYSQL_PASSWORD: foo_pwd
      DNSDOCK_ALIAS: mysql.foo.test
    restart: "unless-stopped"
    networks:
      - web
      - foo

  node:
    container_name: foo_node
    build:
      context: .
      dockerfile: docker/node/Dockerfile
#    image: node:13.0-alpine
    volumes:
      - ./:/app:cached
      - ./node_modules:/app/node_modules:cached
      - ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro,cached
    expose:
      - '9500'
    tty: true
    environment:
      NODE_ENV: development
      DNSDOCK_ALIAS: node.foo.develop
    restart: "unless-stopped"
    networks:
      - foo

  mailhog:
    container_name: foo_mailhog
    image: yappabe/mailhog
    environment:
      DNSDOCK_ALIAS: mailhog.foo.develop
    restart: "unless-stopped"
    networks:
      - foo


networks:
  web:
    external: true
  foo:
    internal: true

I tried several other approaches obviously but none give me the result that I want.

Hi @ixudra,

Is this what you are looking for?

traefik.http.routers.foo-secure.rule=Host(`bar.local`,`foo-status.local`,`bar-status.local`)

@cakiwi Thanks for your reply. I would love for it to be that easy but sadly this is not the case. I still get the same 404 error. I tried it with and without the additional routers and middlewares but the result is always the same.

Could it be I also need to update this line?

      - "traefik.http.middlewares.crm-security.headers.SSLHost=foo.local"

Also, so far I've always done docker-compose stop instead of docker-compose down, I don't think that would make a difference but I thought I would mention it to be sure.

Ah yep, I'm sure this would cause issues. Any reason you have this specifically set?

docker-compose up should detect the updates and redeploy as required. docker-compose start will not

Not particularly... Honestly I'm fairly new and inexperienced in Traefik and I just followed a convoluted list of different tutorials to get where I am today and I'm very much aware that there is plenty of room for improvement.

In any case, I tried removing the line but it makes no difference. Still the 404 errors as before.

The multihost rule definitely 'works' the issue could be in the middlewares, header, your backend service of some combination.

#1 First I would recommend using the traefik accesslog to first ensure your router is handling the request.
#2 Next make the router as simple as possible and then add middlewares, etc.

@cakiwi Thanks again for your reply

Access log doesn't show much info I'm afraid, but it seems the correct router is being used:

172.18.0.1 - - [22/Jan/2021:09:37:30 +0000] "GET /uuid/03b1a601-462a-4860-b552-412121fac960/offer HTTP/2.0" 404 154 "-" "-" 38 "foo-secure@docker" "http://172.18.0.30:80"
172.18.0.1 - - [22/Jan/2021:09:49:42 +0000] "GET /data/747361 HTTP/2.0" 200 65728 "-" "-" 237 "foo-secure@docker" "http://172.18.0.30:80" 2701ms

I also tried removing the middleware and reducing the router to bare minimum but it doesn't make any difference: still 404 error.

      - "traefik.enable=true"
      - "traefik.http.routers.crm-secure.entrypoints=https"
      - "traefik.http.routers.foo-secure.rule=Host(`bar.local`,`foo-status.local`,`bar-status.local`)
      - "traefik.http.routers.crm-secure.tls.certresolver=myhttpschallenge"

So a couple of questions here, do you need all those entrypoints (or routes) to redirect to the same backend?

If using curl to debug (curl -v) are all the endpoints reachable without going through the reverse proxy?

I'm probably old school here but I write my rules like:
"traefik.http.routers.foo-secure.rule="(Host(bar.local) || Host(foo-status.local))"

Not that is related to 404 -- are all your ssl certs generated to have the foo-status.local, bar-status.local on them within the SAN field?

Also your networks. For my network definition (for internal networks or networks defined within the docker-compose file), I usually do the following (you can change docker-net to the name you want, this is example):

networks:
  docker-net:
    name: docker-net
    driver: bridge

Yes

You mean to curl them from my host machine?

This was the first thing that I tried but doesn't work for me. Gives me a 404 error page, not even an nginx page. So my guess is that nginx even fails to start if I do it like this

I doubt it. It's been so long since I did that so I have no idea. Honestly, I just accept the "invalid certificate" warnings on my app and move on, so I doubt this has anything to do with it - I could be wrong

What's the impact of doing it like this? I got this from one of the tutorials I followed and it seems to work just fine, so not sure if this has anything to do with the problem?

Additional info: I just found out that the foo-status.local/something-here returns an nginx error page whereas the foo-status.local page returns the default "Welcome to Nginx" message, which implies that the route is "accepted" but isn't routed to my app, for some reason. I confirmed this by placing a die() statement in my index.php, it was never reached. Does nginx have a problem with placing multiple vhost definitions in a single file?

I don't think nginx has a problem with that although I usually place in different files but I'm aware all the files are concatenated together eventually. I'm not saying traefik is working but it looks like there also might some misconfiguration of the backend as well.

As your logs show the router is handling it the. 404 is actually from nginx.
There was even a 200 OK for the /data/747361 request

Just a final reply to help anyone who may be having this issue: it seems the

traefik.http.routers.foo-secure.rule=Host(`foo.local`,`foo-status.local`,`bar-status.local`)

as suggested by @cakiwi was indeed the solution to my problem. In addition to that, the nginx config needed to be modified to include all urls into a single vhost as mentioned below.

server {
    server_name foo.local foo-status.local bar-status.local;
    root /app/public;

    # More config stuff here

    error_log /var/log/nginx/project_error.log;
    access_log /var/log/nginx/project_access.log;
}

Once I did that, it all worked out perfectly.

I will also add that the middleware line

      - "traefik.http.middlewares.crm-security.headers.SSLHost=foo.local"

did in fact have no effect on the outcome, it works fine with or without it.

So thanks very much to @cakiwi and @kevdog for the assistance, I'm very happy I was able to fix this issue with your help.

You're welcome @ixudra and thanks for following up.

Interesting that you had to collapse to one vhost. I would not expect that, as the host header is forwarded.

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.