Traefik: Custom Error Pages and Returning 503 for Unknown Services

I've been wanting to customize the rather boring default "404 page not found" message that Traefik returns when a service is unknown/unavailable. Occasionally this error is returned when restarting the container running the service or if the container fails to start.

If you have customers visiting some of these services (like I do), then providing some more information on what to do is better than a short and cryptic error message.

ℹ️
If using Cloudflare in front of your server, pay attention to the status codes you return. Cloudflare returns their own error pages for some status codes in 5xx class, except for the 500, 501, 503, and 505codes.

For services that aren't running, the 503 status code is the most optimal, as search engines will see this as a temporary situation and won't impact your site ranking.

In your docker-compose.yaml file, define a new container that will handle catchall requests and error pages. In this example, I've chosen to re-write the path to / for catchall requests (the actual request URI path isn't important).

services:
  nginx-catch-all:
    image: nginx:stable
    volumes:
      - "./catchall/nginx.conf:/etc/nginx/conf.d/default.conf:ro"
      - "./catchall/pages:/usr/share/nginx/html:ro"
    labels:
      traefik.enable: "true"
      traefik.http.routers.catch-all.service: "catch-all@docker"
      traefik.http.routers.catch-all.rule: "PathPrefix(`/`)"
      
      # The priority must be lower than all other services
      traefik.http.routers.catch-all.priority: 1
      traefik.http.routers.catch-all.entrypoints: "https"
      traefik.http.routers.catch-all.middlewares: "catch-all"
      
      traefik.http.services.catch-all.loadbalancer.server.port: 80

      traefik.http.middlewares.catch-all.replacepath.path: "/"

      # This middleware can be applied to other services to return custom error pages, instead of the default/generic pages
      traefik.http.middlewares.error-pages.errors.status: "405,406,408,410,429,451,500-599"
      traefik.http.middlewares.error-pages.errors.service: "catch-all"
      traefik.http.middlewares.error-pages.errors.query: "/{status}.html"
docker-compose.yaml

In the nginx.conf file, we'll setup a basic server that simply returns static html files (you could also serve dynamically generated pages using e.g.: SSI, PHP, Python).

In the location = / block that will be used for the catchall requests, we define a custom error page for the 503 status code and return a 503 Service Unavailable error that Traefik proxies to the user.

server {
        listen 80;
        server_name _;
        error_log stderr warn;
        access_log /dev/stdout main;
        
        error_page 404 /404.html;

        location / {
                root /usr/share/nginx/html;
                internal;
        }

        # Catchall requests will be made to the root, return a 503 status code instead.
        location = / {
                error_page 503 /catchall.html;
                return 503;
        }
}
Nginx configuration

With this configuration, all that is left is to create custom error pages that match the status code, e.g. 404.html and place them in the ./catchall/pages directory.

Now, when you visit the URL of a service that hasn't fully started yet, your custom "catchall" error page will be displayed with a 503 status code.