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/logininstead of maintaining the/newprefix
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_NAMEin Django - Missing
ProxyPassReversefor redirect handling - Incorrect static file paths in both Apache and Django