I needed to serve a custom 503 page for every request to any URL for a domain, and since we serve things using Traefik, I needed a backend service which served the same 503 image for any request.
The vhost configuration for Nginx ended up being like this -
server {
listen 80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
root /usr/share/nginx/html;
# Convert all error codes to 503 and specify file
error_page 404 500 502 503 504 =503 /index.html;
# Add the Retry-After header
add_header Retry-After "86400" always;
# Don't allow external index.html access (otherwise it would give a 200 for index.html)
location = /index.html {
internal;
}
location / {
try_files $uri =503;
}
}
Of note is the error_page directive on line 10, which throws a 503 for any other kind of error. On line 16, we also signify requests to /index.html as internal, so that Nginx doesn’t return a 200 for it. On line 21, we have a try_files directive which tries to serve the file if it exists, otherwise returns a 503. I’m guessing that a try_files =503 might also work, and might be a bit cleaner, but maybe it’s good to keep it on the if we ever need to actually serve anything.
If it isn’t evident enough, the index.html file is the custom 503 page we are serving for all requests.
The docker-compose.yml for the Nginx container ended up looking like this, we use the Docker provider and store routing config in the labels -
notmy-503:
image: nginx:1.29.4
volumes:
- ./notmy-503/vhost.conf:/etc/nginx/conf.d/default.conf
- ./notmy-503/webroot:/usr/share/nginx/html
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.services.notmy-503.loadbalancer.server.port=80"
And my Traefik dynamic configuration had the router defined like so -
### Notmy
notmy-503-redirect-router:
entryPoints:
- https
rule: "Host(`notmy.org`, `www.notmy.org`)"
service: ndltd-503@docker
tls:
certResolver: letsencrypt-http