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)
html
1<!-- 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/status relative to current page
- If page is at
https://example.com/rabbit/, request goes to https://example.com/rabbit/api/status
- Proxy strips
/rabbit/ and forwards to backend
JavaScript Config Method (For Complex Applications)
javascript
1// 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
apache
1# 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/page becomes /page at backend
ProxyPassReverse rewrites response headers (redirects, etc.)
API + Frontend Configuration
File: /etc/apache2/proxy-conf.d/service-with-api.conf
apache
1# 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 /status to port 8765
GET /rabbit/index.html → matches second rule → GET /index.html to port 9273
Large File Upload Configuration
apache
1# 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
apache
1# 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
bash
1# 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
bash
1# 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
bash
1sudo systemctl reload apache2
2
3# Verify Apache is still running
4sudo systemctl status apache2
4. Test Public Access
bash
1# 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:
bash
1ssh rivsprod01 "curl -s http://10.99.0.3:PORT/endpoint"
-
Check Apache config order:
bash
1ssh 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:
bash
1ssh 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:
apache
1# 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:
apache
1# 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:
bash
1# 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:
bash
1ssh 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
python
1# 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.3 or 0.0.0.0 to be accessible from rivsprod01
- Use
http://10.99.0.3:PORT in ProxyPass target URLs
- Never use
localhost or 127.0.0.1 in 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