Reverse Proxy Deployment
Guide for deploying web applications behind the Apache reverse proxy on rivsprod01 (dw.ramsden-international.com). Covers frontend relative URL patterns and Apache ProxyPass configuration.
Instructions
When deploying a web application that will be accessed through the reverse proxy:
- Use Relative URLs: Frontend code must use relative URLs (not absolute localhost URLs) to work behind the proxy
- Configure ProxyPass on rivsprod01: Create Apache config in
/etc/apache2/proxy-conf.d/with proper path stripping - Order Matters: More specific paths must come before general paths in Apache config
- Test Locally First: Verify services are accessible from rivsprod01 before configuring proxy
- Use Trailing Slashes: Both source and target URLs need trailing slashes for path stripping to work
Examples
Example 1: Deploy Web Application with API Backend
User: I have a web app with frontend on port 9273 and API on port 8765. Deploy it behind the proxy at /myapp/
Claude: I'll set up your application for reverse proxy deployment:
1. **Update Frontend URLs**: Change API calls to use relative URLs
- Before: `fetch('http://localhost:8765/api/status')`
- After: `fetch('api/status')` // Relative to current path
2. **Create Apache Config**: SSH to rivsprod01 and create `/etc/apache2/proxy-conf.d/myapp.conf`
- API path: `/myapp/api/` → backend on port 8765
- Frontend path: `/myapp/` → frontend on port 9273
- API location must come FIRST (more specific)
3. **Reload Apache**: `sudo systemctl reload apache2`
[Provides implementation using the ProxyPass Configuration pattern below]
Example 2: Frontend Uses Absolute URLs
User: My frontend has `fetch('http://localhost:3000/api/data')` hardcoded. How do I make it work behind a proxy?
Claude: Absolute URLs won't work behind a reverse proxy. You need to update the frontend to use relative URLs:
**Options:**
1. **Best: Use relative URLs** - `fetch('api/data')` - works anywhere
2. **Alternative: Use base path** - Store base path in config, prepend to all API calls
3. **Hidden input method** - Store proxy path in HTML, read in JavaScript
For option 1 (recommended):
- Replace all `http://localhost:3000/api/` with `api/`
- The browser resolves relative to current page path
- If page is at `/myapp/`, API call goes to `/myapp/api/`
[Shows the Relative URL Pattern implementation below]
Example 3: Multiple Services on Same Machine
User: I have 3 services running on ports 8001, 8002, 8003. Deploy all at /service1/, /service2/, /service3/
Claude: I'll configure all three services in a single Apache config file:
1. **Create single config file**: `/etc/apache2/proxy-conf.d/services.conf`
2. **Add ProxyPass for each**: More specific paths first (doesn't matter here since no overlap)
3. **Each service gets clean URLs**: Paths are stripped before forwarding
4. **Reload once**: All services go live together
[Provides implementation using the Multi-Service Configuration pattern below]
Example 4: API Calls Return 404
User: My frontend loads but API calls return 404. Frontend is at /rabbit/, API should be at /rabbit/api/
Claude: The issue is likely ProxyPass configuration. Let me check:
1. **Verify services are reachable**: From rivsprod01, can you curl the backend directly?
- `curl http://10.99.0.3:8765/status` should work
2. **Check ProxyPass order**: API location must come BEFORE frontend location
- Wrong order: frontend `/rabbit/` catches `/rabbit/api/` requests
- Right order: API `/rabbit/api/` catches first, then frontend `/rabbit/`
3. **Verify trailing slashes**: Both paths need trailing slashes for stripping
- `ProxyPass /rabbit/api/ http://10.99.0.3:8765/`
- Without trailing slash, path isn't stripped properly
[Shows debugging steps from Troubleshooting section below]
Reference Implementation Details
The sections below contain proven working configurations from production deployments.
Reference Files in This Folder:
rabbit.conf- Example Apache config from Invoice OCR deployment
Relative URL Pattern
Purpose: Make frontend work behind any reverse proxy path without hardcoding URLs
HTML Method (Recommended for Simple Cases)
html1<!-- Hidden input stores the API base path --> 2<input type="hidden" id="serverUrl" value="api"> 3 4<script> 5function getServerUrl() { 6 return document.getElementById('serverUrl').value.trim(); 7} 8 9// Use in fetch calls 10async function checkStatus() { 11 const res = await fetch(`${getServerUrl()}/status`); 12 const data = await res.json(); 13 // ... 14} 15</script>
Key Points:
- Value is
"api"(relative) not"http://localhost:8765"(absolute) - Browser resolves
api/statusrelative to current page - If page is at
https://example.com/rabbit/, request goes tohttps://example.com/rabbit/api/status - Proxy strips
/rabbit/and forwards to backend
JavaScript Config Method (For Complex Applications)
javascript1// config.js 2const CONFIG = { 3 API_BASE: 'api', // Relative to current path 4 WS_BASE: 'ws' // WebSocket endpoint if needed 5}; 6 7// Use throughout app 8fetch(`${CONFIG.API_BASE}/endpoint`)
When to use:
- Multiple API endpoints
- Different base paths for dev/staging/prod
- Need to change paths without editing HTML
ProxyPass Configuration Pattern
Purpose: Configure Apache to forward requests from public path to internal service
Basic Single-Service Configuration
File: /etc/apache2/proxy-conf.d/myservice.conf
apache1# Single service on custom path 2ProxyPass /myservice/ http://10.99.0.3:8080/ 3ProxyPassReverse /myservice/ http://10.99.0.3:8080/
Key Points:
- Simple ProxyPass directives (no
<Location>blocks needed) - Trailing slashes on both source and target strip the path prefix
- Request to
/myservice/pagebecomes/pageat backend ProxyPassReverserewrites response headers (redirects, etc.)
API + Frontend Configuration
File: /etc/apache2/proxy-conf.d/service-with-api.conf
apache1# Backend API - Must come FIRST (more specific path) 2ProxyPass /rabbit/api/ http://10.99.0.3:8765/ 3ProxyPassReverse /rabbit/api/ http://10.99.0.3:8765/ 4 5# Frontend - Comes SECOND (less specific path) 6ProxyPass /rabbit/ http://10.99.0.3:9273/ 7ProxyPassReverse /rabbit/ http://10.99.0.3:9273/
Key Points:
- API location MUST be listed first (more specific)
- If frontend is listed first, it catches API requests → 404
- Both locations strip their prefix before forwarding
- Request flow:
GET /rabbit/api/status→ matches first rule →GET /statusto port 8765GET /rabbit/index.html→ matches second rule →GET /index.htmlto port 9273
Large File Upload Configuration
apache1# Service that handles file uploads (PDFs, images, etc.) 2ProxyPass /uploads/ http://10.99.0.3:7000/ 3ProxyPassReverse /uploads/ http://10.99.0.3:7000/ 4 5# Important for large PDF uploads 6<Location /uploads/> 7 ProxyPass http://10.99.0.3:7000/ 8 ProxyPassReverse http://10.99.0.3:7000/ 9 10 # Allow 50MB uploads 11 LimitRequestBody 52428800 12</Location>
When to use:
- File upload services
- PDF/image processing
- Any endpoint that receives large request bodies
Multi-Service Configuration Pattern
Purpose: Deploy multiple independent services in one config file
File: /etc/apache2/proxy-conf.d/all-services.conf
apache1# Service 1: API Gateway 2ProxyPass /api/ http://10.99.0.3:8001/ 3ProxyPassReverse /api/ http://10.99.0.3:8001/ 4 5# Service 2: Admin Dashboard 6ProxyPass /admin/ http://10.99.0.3:8002/ 7ProxyPassReverse /admin/ http://10.99.0.3:8002/ 8 9# Service 3: Public Website 10ProxyPass /site/ http://10.99.0.3:8003/ 11ProxyPassReverse /site/ http://10.99.0.3:8003/
Key Points:
- Each service is completely independent
- Order doesn't matter if paths don't overlap
- All go live together when Apache reloads
- Can comment out individual services to disable temporarily
Deployment Workflow
Standard deployment process for new service:
1. Verify Local Service Accessibility
bash1# From local machine where service runs 2curl http://10.99.0.3:8765/status # Test your port 3 4# From rivsprod01 (SSH in) 5ssh rivsprod01 "curl -s http://10.99.0.3:8765/status"
Expected: JSON response or HTML, not connection refused
2. Create Apache Configuration
bash1# SSH to rivsprod01 2ssh rivsprod01 3 4# Create config file (as root or with sudo) 5sudo nano /etc/apache2/proxy-conf.d/myservice.conf 6 7# Add ProxyPass configuration (see patterns above) 8 9# Verify syntax (optional, but recommended) 10# apache2ctl configtest # May not be available 11# Just proceed to reload if command not found
3. Reload Apache
bash1sudo systemctl reload apache2 2 3# Verify Apache is still running 4sudo systemctl status apache2
4. Test Public Access
bash1# From any machine 2curl https://dw.ramsden-international.com/myservice/ 3 4# Or open in browser
Troubleshooting
404 Not Found - Service Works Locally
Cause: ProxyPass configuration issue
Diagnostic Steps:
-
Verify service is accessible from rivsprod01:
bash1ssh rivsprod01 "curl -s http://10.99.0.3:PORT/endpoint" -
Check Apache config order:
bash1ssh rivsprod01 "cat /etc/apache2/proxy-conf.d/myservice.conf"- API paths must come before frontend paths
- Verify trailing slashes on both source and target
-
Check Apache error logs:
bash1ssh rivsprod01 "sudo tail -50 /var/log/apache2/error.log | grep myservice"
Solution:
- Fix ProxyPass order (more specific first)
- Add missing trailing slashes
- Reload Apache:
sudo systemctl reload apache2
404 on API Calls, Frontend Works
Cause: Frontend path is catching API requests
Example of Wrong Configuration:
apache1# WRONG - Frontend catches everything 2ProxyPass /app/ http://10.99.0.3:9000/ 3ProxyPass /app/api/ http://10.99.0.3:8000/
Solution - Put API First:
apache1# CORRECT - API catches specific path first 2ProxyPass /app/api/ http://10.99.0.3:8000/ 3ProxyPass /app/ http://10.99.0.3:9000/
Connection Refused from rivsprod01
Cause: Service not listening on accessible IP
Diagnostic:
bash1# On machine running service 2netstat -tlnp | grep PORT 3# or 4ss -tlnp | grep PORT
Look for:
127.0.0.1:PORT- Only listening on localhost (wrong)10.99.0.3:PORT- Listening on network IP (correct)0.0.0.0:PORT- Listening on all interfaces (also correct)
Solution: Configure service to bind to 0.0.0.0 or specific IP 10.99.0.3
Service Stops Working After Apache Reload
Cause: Configuration syntax error
Diagnostic:
bash1ssh rivsprod01 "sudo systemctl status apache2"
Solution:
- Check for typos in ProxyPass URLs
- Verify no missing quotes or slashes
- Look at error log:
sudo tail /var/log/apache2/error.log - Fix config and reload again
CORS Errors in Browser Console
Cause: Backend not configured for CORS, or wrong origin
Solution (Backend): Enable CORS in your service
python1# FastAPI example 2from fastapi.middleware.cors import CORSMiddleware 3 4app.add_middleware( 5 CORSMiddleware, 6 allow_origins=["*"], # Or specific domain 7 allow_credentials=True, 8 allow_methods=["*"], 9 allow_headers=["*"], 10)
Note: Proxy handles forwarding; backend sees requests from proxy IP, not client
IP Addresses Reference
| Hostname | IP Address | Purpose |
|---|---|---|
| rivsprod01 | 10.99.0.2 | Reverse proxy server (Apache) |
| pogs (local) | 10.99.0.3 | Development machine running services |
Service Configuration:
- Services should bind to
10.99.0.3or0.0.0.0to be accessible from rivsprod01 - Use
http://10.99.0.3:PORTin ProxyPass target URLs - Never use
localhostor127.0.0.1in ProxyPass targets
File Locations on rivsprod01
| Path | Purpose |
|---|---|
/etc/apache2/proxy-conf.d/*.conf | Proxy configurations (add yours here) |
/etc/apache2/sites-available/default-ssl.conf | Main SSL site config (includes proxy-conf.d) |
/var/log/apache2/error.log | Apache error log |
/var/log/apache2/access.log | Access log (if needed) |
Important: Configs in /etc/apache2/proxy-conf.d/ are automatically included by the SSL site configuration.
Best Practices Summary
- Always use relative URLs in frontend code for proxy compatibility
- Test service accessibility from rivsprod01 before configuring proxy
- Put specific paths first in Apache config (API before frontend)
- Use trailing slashes on both ProxyPass source and target for path stripping
- Reload Apache after config changes:
sudo systemctl reload apache2 - Keep configs organized - one file per application or related service group
- Document your paths - add comments explaining what each ProxyPass does
- Test both frontend and API after deployment to verify routing