ICVOSS DJANGO PACKAGE REGISTRY

The package index django-hostmap Run behind a proxy

Run behind a proxy

Documentation

Goal

Deploy hostmap behind nginx (or any reverse proxy) so the Host header Django sees is the one your users actually requested, not the proxy's own internal hostname.

This is the single most common support issue with host-based routing of any kind: everything works in runserver and breaks, silently misrouting every request to the default entry, the moment a proxy sits in front of Django.

Prerequisites

Steps

1. Understand the failure mode first

Host routing is only as good as the Host header that reaches Django. HostmapMiddleware matches on request.get_host(), and request.get_host() reads the Host header (or X-Forwarded-Host, if configured; see step 3), never anything else. If the proxy rewrites or drops that header before forwarding the request, every request looks like it came in on whatever host the proxy used internally, usually the default entry or an unmatched host.

2. Confirm the proxy passes the original Host header through unchanged

For nginx, this means the proxy_pass block explicitly forwards Host:

location / {
    proxy_pass http://django_upstream;
    proxy_set_header Host $host;
}

Without proxy_set_header Host $host;, nginx defaults to forwarding the value from proxy_pass's own upstream definition in some configurations, which is not what you want.

3. Or use X-Forwarded-Host with USE_X_FORWARDED_HOST

If your proxy sets X-Forwarded-Host instead of passing Host through directly (common with some load balancers and CDNs), tell Django to trust it:

# settings.py
USE_X_FORWARDED_HOST = True

request.get_host() then reads X-Forwarded-Host when present. Only enable this if you have verified the proxy is the sole entry point and it always sets this header itself; otherwise a client could forge it directly.

4. Confirm ALLOWED_HOSTS matches what the proxy forwards, not the upstream address

ALLOWED_HOSTS validates the same header hostmap routes on. If the proxy forwards www.example.com but ALLOWED_HOSTS only lists an internal upstream name, Django rejects the request with a 400 before hostmap ever runs.

5. Decide what happens to a host past ALLOWED_HOSTS but absent from the map

An allowed-but-unmapped host is served by the default entry under HOSTMAP_UNMATCHED = "default" (the default setting). This is deliberate: it means a typo'd or unexpected but otherwise valid host still gets a response rather than a hard failure. For strict deployments where an unmapped host should be rejected outright, set:

HOSTMAP_UNMATCHED = "reject"

This raises Http404 for any host that matches no entry at all.

Verify it worked

From outside the proxy:

curl -H "Host: api.example.com" https://your-proxy/users/7/

Then confirm on the Django side (logging, or a temporary debug view) that request.get_host() actually returns api.example.com, not the proxy's own hostname. manage.py hostmap confirms what hostmap expects to see; it does not confirm what the proxy is actually sending, so check both.

Common pitfalls