Skip to main content

Sarah's Journey: Understanding Proxies Through Real-World Challenges

The Urgent Call

Sarah stared at her laptop screen, coffee growing cold beside her. It was 2 AM on a Thursday, and her phone wouldn't stop buzzing. The ML model deployment she'd been working on for weeks was finally live, but something was terribly wrong.

"Sarah, we're getting timeout errors on the API," her teammate Mike's message read. "Users in Europe can't access the service at all. The load is killing our single server."

She'd seen this coming. The team had rushed the deployment, and she'd warned them about bottlenecks. Now, with thousands of users trying to access their new image classification API simultaneously, their single backend server was buckling under pressure.

Sarah took a deep breath. She'd heard about proxies solving these exact problems, but had never implemented one herself. Tonight, that was about to change.

First Attempt: The Naive Approach

Sarah's first instinct was simple: just add more backend servers and have the frontend rotate between them. She quickly spun up two additional API servers and modified the client code to randomly select a server.

# client.py - Sarah's first attempt
import random
import requests

API_SERVERS = [
"http://api-server-1.company.com:8080",
"http://api-server-2.company.com:8080",
"http://api-server-3.company.com:8080"
]

def call_api(image_data):
# Randomly pick a server
server = random.choice(API_SERVERS)
response = requests.post(f"{server}/classify", json={"image": image_data})
return response.json()

She deployed the update and watched the metrics. For a brief moment, things seemed better. Then the problems started:

  • When api-server-2 went down for a health check, users got cryptic connection errors
  • Mobile clients had to be updated to use the new logic
  • SSL certificates had to be managed on three different servers
  • She had no visibility into which server was actually handling requests
  • Some servers were getting hammered while others sat idle

"This is chaos," Sarah muttered, rubbing her tired eyes. There had to be a better way.

The Learning Moment: Discovery of Proxies

The next morning, Sarah called her mentor, James, a senior DevOps architect. He laughed when she explained her random server selection approach.

"Sarah, you've just reinvented the wheel, poorly," James said kindly. "What you need is a reverse proxy. Think of it as a smart receptionist for your backend servers."

James explained the concept: instead of clients knowing about multiple backend servers, they'd talk to a single proxy server. The proxy would then intelligently distribute requests to healthy backend servers, handle SSL, and provide a single point of monitoring.

"It's like a load balancer?" Sarah asked.

"Exactly! A reverse proxy can act as a load balancer, among other things," James confirmed. "Let me show you how to set one up with Nginx."

Sarah's mind raced with the possibilities. One entry point, intelligent distribution, centralized SSL, health checks... This was exactly what she needed.

Understanding the Architecture

James sketched out the architecture on their video call:

Internet Users (Worldwide)

[Reverse Proxy - Nginx]
↓ ↓ ↓
[Server1] [Server2] [Server3]
(US-East) (US-West) (EU-West)

"The reverse proxy sits between your users and your backend servers," James explained. "It receives all incoming requests and forwards them to available backend servers using algorithms like round-robin, least connections, or IP hash."

Sarah scribbled notes furiously. This was different from what she'd built in two key ways:

  1. Single Entry Point: Users only know about one address - the proxy
  2. Intelligence: The proxy can detect failed servers and avoid sending traffic to them

"There's also the forward proxy," James added, "but that's the opposite - it sits on the client side, making requests on behalf of clients. Think of corporate networks that filter internet access."

Building the Solution

Sarah rolled up her sleeves and started configuring Nginx as a reverse proxy. She created her configuration file carefully:

# /etc/nginx/nginx.conf
http {
# Define upstream backend servers
upstream ml_api_backend {
# Use least_conn algorithm for better distribution
least_conn;

# Define backend servers with health check parameters
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}

# Define the proxy server
server {
listen 80;
server_name api.company.com;

# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name api.company.com;

# SSL configuration (centralized!)
ssl_certificate /etc/nginx/ssl/api.company.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.company.com.key;
ssl_protocols TLSv1.2 TLSv1.3;

# Proxy configuration
location /classify {
proxy_pass http://ml_api_backend;

# Forward important headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

# Handle large image uploads
client_max_body_size 10M;
}

# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}

As Sarah typed each line, she understood what she was building:

  • upstream ml_api_backend: Defined her three backend servers as a pool
  • least_conn: Ensured requests went to the server with fewest active connections
  • max_fails & fail_timeout: Automatically removed unhealthy servers from rotation
  • SSL termination: Handled HTTPS at the proxy level, keeping backend servers simple
  • Header forwarding: Ensured backend servers knew the real client IP

She tested the configuration syntax:

# Test Nginx configuration
sudo nginx -t

# Output:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

Perfect! Now to apply it:

# Reload Nginx with new configuration
sudo systemctl reload nginx

# Check status
sudo systemctl status nginx

The Resolution: Watching It Work

Sarah opened her monitoring dashboard and updated the client code to use the new single endpoint:

# client.py - The elegant solution
import requests

API_ENDPOINT = "https://api.company.com"

def call_api(image_data):
# Simple! Just one endpoint
response = requests.post(f"{API_ENDPOINT}/classify", json={"image": image_data})
return response.json()

She sent a test request. The proxy received it, selected a backend server using the least-connection algorithm, and forwarded the request. The response came back instantly.

She watched as thousands of requests flowed through the system:

  • Requests were evenly distributed across all three servers
  • When she took server-2 down for maintenance, Nginx automatically stopped sending traffic to it
  • SSL was managed in one place
  • She could see all traffic patterns in Nginx's access logs
  • Response times improved by 40%

European users reported the service was now fast and reliable. The timeout errors vanished.

Sarah leaned back in her chair, allowing herself a small smile. The system that had been failing hours ago was now humming along smoothly, handling 10x the traffic without breaking a sweat.

Reflection: What Sarah Learned

As Sarah documented her solution for the team, she reflected on the key lessons:

Understanding Proxy Types

Forward Proxy (Client-side):

  • Sits between clients and the internet
  • Clients must be configured to use it
  • Use cases: Content filtering, bandwidth savings, privacy
  • Example: Corporate network proxy for web browsing

Reverse Proxy (Server-side):

  • Sits between the internet and backend servers
  • Transparent to clients (they don't know it exists)
  • Use cases: Load balancing, SSL termination, security, caching
  • Example: What Sarah built with Nginx

Key Benefits Realized

  1. Single Point of Entry: Clients only need to know one address
  2. Automatic Failover: Unhealthy servers are automatically bypassed
  3. SSL Termination: Manage certificates in one place instead of on every backend
  4. Load Distribution: Intelligent algorithms ensure even distribution
  5. Observability: Centralized logging and monitoring
  6. Security: Hide backend infrastructure, add rate limiting, filtering

The Implementation Details That Mattered

# The load balancing algorithm choice matters
least_conn; # Route to server with fewest active connections
# vs
# round_robin; # Default - rotate servers sequentially
# vs
# ip_hash; # Same client IP always goes to same server

Sarah realized that choosing least_conn was crucial for her ML API because inference times varied based on image complexity. This algorithm ensured no single server got overwhelmed with slow requests.

Expanding Knowledge: Forward Proxy Example

Curious about the other side of the proxy world, Sarah experimented with a forward proxy. She set up Squid to control and monitor outbound traffic from her development team:

# Install Squid proxy
sudo apt install squid -y

# Basic configuration /etc/squid/squid.conf
http_port 3128

# Define allowed networks
acl localnet src 192.168.1.0/24

# Allow local network
http_access allow localnet
http_access deny all

# Cache configuration
cache_dir ufs /var/spool/squid 1000 16 256

Now developers could configure their browsers or tools to use the proxy:

# Use forward proxy with curl
curl -x http://proxy.company.local:3128 https://external-api.com/data

# Set environment variables for system-wide proxy
export http_proxy=http://proxy.company.local:3128
export https_proxy=http://proxy.company.local:3128

This gave the team visibility into what external services were being called and allowed them to cache frequently accessed resources.

What You've Learned

Through Sarah's journey, you've discovered:

ConceptDescriptionKey Use Case
Reverse ProxyServer-side intermediary between clients and backend serversLoad balancing, SSL termination, security
Forward ProxyClient-side intermediary between clients and the internetAccess control, caching, monitoring outbound traffic
Load Balancing AlgorithmsMethods to distribute traffic (round-robin, least_conn, ip_hash)Optimize server utilization and response times
Health ChecksAutomatic detection of failed serversHigh availability and automatic failover
SSL TerminationHandling HTTPS at proxy levelSimplified certificate management
Header ForwardingPassing client information to backendsLogging real client IPs, maintaining request context

Key Configuration Patterns

For Web Applications:

upstream backend {
least_conn; # Best for varying processing times
server backend1:8080;
server backend2:8080;
}

For API Services:

upstream api {
ip_hash; # Session persistence for stateful APIs
server api1:8080;
server api2:8080;
}

For Static Content:

upstream static {
# Simple round-robin works well
server static1:8080;
server static2:8080;
}

Best Practices Sarah Discovered

  1. Always use health checks - Let the proxy automatically detect and avoid failed servers
  2. Forward client information - Backend servers need X-Forwarded-For and X-Real-IP headers
  3. Set appropriate timeouts - Match them to your application's needs
  4. Enable logging - You need visibility into traffic patterns
  5. Use SSL termination - Simplify certificate management
  6. Choose the right algorithm - Consider your workload characteristics
  7. Plan for maintenance - Proxies allow zero-downtime deployments

Sarah's experience transformed her understanding of proxies from theoretical concept to practical tool. What started as a crisis at 2 AM became a valuable lesson in designing resilient, scalable systems.

The next time you face scaling challenges, remember Sarah's journey: sometimes the best solution isn't more servers, but smarter routing between them.

Want to try it yourself? Start with a simple Nginx reverse proxy in a test environment, send some requests through it, and watch how it distributes load. There's no better way to learn than by building it yourself.