When setting up reverse proxy configurations with Apache's mod_proxy
, many developers encounter URL path issues when applications perform redirects. The core issue manifests when:
- Your main application (e.g., PHP) runs on Machine #1
- A secondary application (e.g., Django) runs on Machine #2
- You configure
ProxyPass /new http://192.168.0.101/
- The Django app generates redirects like
/auth/login
instead of maintaining the/new
prefix
Django's URL resolution works independently of your proxy configuration. When it generates redirects:
# Django's view.py example def login_redirect(request): return HttpResponseRedirect('/auth/login/') # This breaks proxy paths
The redirect doesn't account for your /new
prefix because Django only sees the request path after ProxyPass processing.
To properly handle this, we need both ProxyPass and ProxyPassReverse:
<VirtualHost *:80> ServerName foo.net # Main PHP app DocumentRoot /var/www/php_app # Django proxy configuration ProxyPass /new http://192.168.0.101/ ProxyPassReverse /new http://192.168.0.101/ # Additional headers to help with URL generation ProxyPreserveHost On RequestHeader set X-Forwarded-Proto "http" RequestHeader set X-Forwarded-Prefix "/new" </VirtualHost>
Configure Django to recognize proxy headers:
# settings.py USE_X_FORWARDED_HOST = True FORCE_SCRIPT_NAME = '/new' # Only if you want all URLs prefixed # Alternative middleware approach MIDDLEWARE += [ 'django.middleware.common.BrokenLinkEmailsMiddleware', 'django.middleware.common.CommonMiddleware', ]
When using URL reversing in Django templates:
{% raw %} # Good (works with proxy) <a href="{% url 'login' %}">Login</a> # Bad (hardcoded paths break with proxy) <a href="/auth/login/">Login</a> {% endraw %}
Verify with curl commands:
# Test basic proxy functionality curl -I http://foo.net/new/ # Check redirect headers curl -I http://foo.net/new/auth/login/ # Verify X-Forwarded headers curl -H "X-Forwarded-Proto: https" -I http://foo.net/new/
For more control, create custom middleware:
# middleware.py class URLPrefixMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): request.path_info = '/new' + request.path_info return self.get_response(request)
Add to MIDDLEWARE
after CommonMiddleware
.
When configuring Apache's mod_proxy
to route requests to a Django application, the default ProxyPass
behavior often breaks Django's URL routing. The issue manifests when Django generates absolute URLs without accounting for the proxy path prefix (/new
in your case).
Here's the minimal working configuration that preserves URL structure:
<VirtualHost *:80> ServerName foo.net ProxyPass /new http://192.168.0.101/ ProxyPassReverse /new http://192.168.0.101/ # Critical for Django static files Alias /new/static /path/to/django/static <Directory /path/to/django/static> Require all granted </Directory> </VirtualHost>
Your settings.py
needs these modifications:
# Required for proxy setups USE_X_FORWARDED_HOST = True FORCE_SCRIPT_NAME = '/new' # Important for URL generation STATIC_URL = FORCE_SCRIPT_NAME + '/static/' MEDIA_URL = FORCE_SCRIPT_NAME + '/media/'
For complex cases, implement middleware to handle URL rewriting:
class ProxyPathMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): request.META['SCRIPT_NAME'] = '/new' response = self.get_response(request) if 'Location' in response: response['Location'] = '/new' + response['Location'] return response
Verify your setup with these curl commands:
curl -I http://foo.net/new/admin/ curl -v http://foo.net/new/static/css/styles.css
Look for proper 200 responses and correct Content-Type headers.
- Forgetting trailing slashes in ProxyPass directives
- Not setting
FORCE_SCRIPT_NAME
in Django - Missing
ProxyPassReverse
for redirect handling - Incorrect static file paths in both Apache and Django