Set up Apache HTTP Server as a reverse proxy with intelligent caching for microservices architectures. This tutorial covers mod_proxy, mod_cache configuration, cache policies, and monitoring for high-performance service delivery.
Prerequisites
- Root or sudo access
- Basic understanding of HTTP and web services
- Microservices running on different ports
What this solves
A reverse proxy with caching reduces load on backend microservices by serving cached responses for repeated requests. Apache's mod_proxy and mod_cache modules provide flexible routing, load balancing, and intelligent caching that can dramatically improve response times and reduce resource usage across your service architecture.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you have the latest security patches and package versions.
sudo apt update && sudo apt upgrade -y
Install Apache HTTP Server
Install Apache web server which includes the proxy and caching modules we need.
sudo apt install -y apache2 apache2-utils
Enable required Apache modules
Enable the proxy, cache, and related modules needed for reverse proxy functionality with caching.
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod cache
sudo a2enmod cache_disk
sudo a2enmod headers
sudo a2enmod rewrite
Create cache directory
Create a dedicated directory for Apache to store cached content with proper permissions.
sudo mkdir -p /var/cache/apache2/mod_cache_disk
sudo chown www-data:www-data /var/cache/apache2/mod_cache_disk
sudo chmod 755 /var/cache/apache2/mod_cache_disk
Configure main Apache settings
Configure the main Apache configuration with performance optimizations for proxy and caching workloads.
# Add these settings at the end of the file
Performance tuning for proxy workloads
MaxRequestWorkers 400
ThreadsPerChild 25
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
Proxy timeout settings
ProxyTimeout 300
ProxyPreserveHost On
Security headers
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy strict-origin-when-cross-origin
Create virtual host configuration
Create a virtual host that configures reverse proxy with caching for your microservices.
ServerName api.example.com
DocumentRoot /var/www/html
# Enable caching
CacheEnable disk /
CacheRoot /var/cache/apache2/mod_cache_disk
CacheDirLevels 2
CacheDirLength 1
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheMinExpire 60
CacheDetailHeader on
CacheHeader on
# Cache policies for different content types
CacheDefaultExpire 604800
ExpiresActive On
ExpiresDefault "access plus 1 week"
Header set Cache-Control "public, max-age=604800"
CacheDefaultExpire 300
Header set Cache-Control "public, max-age=300"
# Don't cache POST/PUT/DELETE requests
SetEnvIf Request_Method "^(POST|PUT|DELETE)$" no-cache
CacheDisable env=no-cache
# Microservice routing
# User service
ProxyPass /api/users http://localhost:3001/
ProxyPassReverse /api/users http://localhost:3001/
# Product service with load balancing
ProxyPass /api/products balancer://products-cluster/
ProxyPassReverse /api/products balancer://products-cluster/
# Order service
ProxyPass /api/orders http://localhost:3003/
ProxyPassReverse /api/orders http://localhost:3003/
# Health checks bypass cache
CacheDisable on
ProxyPass http://localhost:3000/health
ProxyPassReverse http://localhost:3000/health
# Configure balancer cluster for products
BalancerMember http://localhost:3002 route=1
BalancerMember http://localhost:3022 route=2
ProxySet lbmethod=byrequests
ProxySet hcmethod=GET
ProxySet hcuri=/health
# Logging
LogLevel info
ErrorLog ${APACHE_LOG_DIR}/microservices_error.log
CustomLog ${APACHE_LOG_DIR}/microservices_access.log combined
CustomLog ${APACHE_LOG_DIR}/microservices_cache.log \
"%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %{cache-status}e"
# Security settings
Header always set X-Proxy-Cache %{cache-status}e
Header unset Server
ServerTokens Prod
Create cache directory for Red Hat systems
On Red Hat-based systems, create the cache directory with SELinux context.
# Cache directory already created in previous step
Enable the virtual host
Enable the new virtual host configuration and disable the default site.
sudo a2ensite microservices-proxy.conf
sudo a2dissite 000-default.conf
Configure cache cleanup
Set up automatic cache cleanup to prevent disk space issues. Create a systemd timer to clean old cache files.
[Unit]
Description=Apache Cache Cleanup
After=network.target
[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/bin/find /var/cache/apache2/mod_cache_disk -type f -mtime +7 -delete
ExecStart=/usr/bin/find /var/cache/apache2/mod_cache_disk -type d -empty -delete
[Unit]
Description=Run Apache Cache Cleanup Daily
Requires=apache-cache-cleanup.service
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now apache-cache-cleanup.timer
Test configuration and start Apache
Test the Apache configuration for syntax errors and start the service.
sudo apache2ctl configtest
sudo systemctl enable --now apache2
sudo systemctl status apache2
Configure firewall rules
Open HTTP and HTTPS ports in the firewall for web traffic.
sudo ufw allow 'Apache Full'
sudo ufw reload
Configure cache policies and optimization
Advanced cache configuration
Fine-tune cache behavior with advanced policies for different types of content and API responses.
# Cache size limits and memory settings
CacheMaxFileSize 10000000
CacheMinFileSize 1
CacheReadSize 102400
CacheReadTime 3600
Ignore certain headers that prevent caching
CacheIgnoreHeaders Set-Cookie
CacheIgnoreQueryString Off
CacheKeyBaseURL http://api.example.com
Cache based on Accept-Encoding for compressed content
CacheStorePrivate On
CacheStoreNoStore Off
Advanced cache policies
CacheDefaultExpire 2592000
Header set Cache-Control "public, max-age=2592000, immutable"
ExpiresActive On
ExpiresDefault "access plus 30 days"
CacheDefaultExpire 900
Header set Cache-Control "private, max-age=900"
CacheStorePrivate On
CacheDefaultExpire 1800
Header set Cache-Control "public, max-age=1800"
# Cache different responses for different query parameters
CacheIgnoreQueryString Off
Don't cache authentication endpoints
CacheDisable on
Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache"
Enable cache optimization
Enable the cache optimization configuration and reload Apache.
sudo a2enconf cache-optimization
sudo apache2ctl configtest
sudo systemctl reload apache2
Monitor and troubleshoot reverse proxy
Set up cache monitoring script
Create a monitoring script to track cache performance and hit rates.
#!/bin/bash
Apache cache monitoring script
CACHE_DIR="/var/cache/apache2/mod_cache_disk"
LOG_FILE="/var/log/apache2/microservices_cache.log"
Cache disk usage
echo "Cache Directory Usage:"
du -sh $CACHE_DIR
echo ""
Cache files count
echo "Cache Files Count:"
find $CACHE_DIR -type f | wc -l
echo ""
Recent cache statistics from logs
echo "Cache Hit/Miss Statistics (last 1000 requests):"
if [ -f "$LOG_FILE" ]; then
tail -1000 $LOG_FILE | awk '{
if ($NF ~ /HIT/) hits++;
else if ($NF ~ /MISS/) misses++;
total++
} END {
if (total > 0) {
hit_rate = (hits/total)*100;
printf "Hits: %d (%.1f%%)\n", hits, hit_rate;
printf "Misses: %d (%.1f%%)\n", misses, 100-hit_rate;
printf "Total: %d\n", total;
} else {
print "No cache data found in logs";
}
}'
else
echo "Cache log file not found"
fi
Top requested URLs being cached
echo ""
echo "Top 10 Cached URLs:"
if [ -f "$LOG_FILE" ]; then
tail -1000 $LOG_FILE | awk '{print $7}' | sort | uniq -c | sort -nr | head -10
fi
sudo chmod +x /usr/local/bin/apache-cache-stats.sh
Configure log rotation
Set up log rotation to prevent cache and access logs from consuming too much disk space.
/var/log/apache2/microservices_*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 644 www-data adm
postrotate
if systemctl is-active apache2 > /dev/null; then
systemctl reload apache2
fi
endscript
}
Create health check endpoint
Set up a simple health check that monitors both proxy and cache functionality.
'healthy',
'timestamp' => date('c'),
'checks' => []
];
// Check if cache directory is writable
$cache_dir = '/var/cache/apache2/mod_cache_disk';
$health['checks']['cache_writable'] = is_writable($cache_dir);
// Check cache directory size
$cache_size = shell_exec("du -sb $cache_dir | cut -f1");
$health['checks']['cache_size_bytes'] = intval(trim($cache_size));
$health['checks']['cache_size_mb'] = round($cache_size / 1024 / 1024, 2);
// Check if any backend services are responding
$backends = [
'users' => 'http://localhost:3001/health',
'products' => 'http://localhost:3002/health',
'orders' => 'http://localhost:3003/health'
];
foreach ($backends as $name => $url) {
$context = stream_context_create([
'http' => [
'timeout' => 2,
'ignore_errors' => true
]
]);
$result = @file_get_contents($url, false, $context);
$health['checks']['backend_' . $name] = $result !== false;
}
// Set overall status
foreach ($health['checks'] as $check => $status) {
if (!$status && $check !== 'cache_size_bytes' && $check !== 'cache_size_mb') {
$health['status'] = 'degraded';
break;
}
}
echo json_encode($health, JSON_PRETTY_PRINT);
?>
sudo chown www-data:www-data /var/www/html/proxy-health.php
sudo chmod 644 /var/www/html/proxy-health.php
Verify your setup
Test your Apache reverse proxy configuration and caching functionality.
# Check Apache status and configuration
sudo systemctl status apache2
sudo apache2ctl -S
Test basic proxy functionality
curl -H "Host: api.example.com" http://localhost/health
Test cache headers (run twice to see cache hit)
curl -I -H "Host: api.example.com" http://localhost/api/users
curl -I -H "Host: api.example.com" http://localhost/api/users
Check cache statistics
sudo /usr/local/bin/apache-cache-stats.sh
View recent access logs
sudo tail -f /var/log/apache2/microservices_access.log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway errors | Backend services not running | Check backend service status with systemctl status service-name |
| Cache not working (always MISS) | Backend sends no-cache headers | Add CacheIgnoreHeaders directive for specific headers |
| Permission denied on cache directory | Wrong ownership or SELinux context | Fix with chown www-data:www-data and SELinux contexts |
| Cache fills up disk space | No cache cleanup configured | Ensure cache cleanup timer is active: systemctl status apache-cache-cleanup.timer |
| Slow response times | Insufficient Apache workers | Increase MaxRequestWorkers in Apache configuration |
| Module not found errors | Required modules not enabled | Enable modules with a2enmod module-name on Debian/Ubuntu |
| Balancer not distributing load | Health checks failing | Check backend health endpoints return 200 status |
| SSL termination issues | Missing SSL configuration | Configure SSL virtual host with certificates |
Next steps
- Implement Apache load balancing with SSL termination and health checks for production-ready HTTPS
- Configure Apache security headers and Content Security Policy for enhanced web application protection
- Implement Apache log analysis with GoAccess and ELK stack for detailed request monitoring
- Configure SSL certificates for Apache microservices proxy with Let's Encrypt automation
- Set up Apache Prometheus monitoring and alerting for comprehensive observability
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Apache Reverse Proxy with Caching Setup Script
# Configures Apache with mod_proxy and mod_cache for microservices
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Cleanup function for error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Check logs above for details.${NC}"
exit 1
}
trap cleanup ERR
# Usage message
usage() {
echo "Usage: $0 <domain_name> [backend_service_1:port] [backend_service_2:port] ..."
echo "Example: $0 api.example.com localhost:3000 localhost:3001"
exit 1
}
# Check arguments
if [ $# -lt 2 ]; then
usage
fi
DOMAIN_NAME="$1"
shift
BACKEND_SERVICES=("$@")
# Check if running as root or with sudo
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[ERROR] This script must be run as root or with sudo${NC}"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
APACHE_SERVICE="apache2"
APACHE_CONFIG_DIR="/etc/apache2"
SITES_DIR="/etc/apache2/sites-available"
SITES_ENABLED="/etc/apache2/sites-enabled"
APACHE_USER="www-data"
USE_A2ENMOD=true
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
APACHE_SERVICE="httpd"
APACHE_CONFIG_DIR="/etc/httpd"
SITES_DIR="/etc/httpd/conf.d"
APACHE_USER="apache"
USE_A2ENMOD=false
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
APACHE_SERVICE="httpd"
APACHE_CONFIG_DIR="/etc/httpd"
SITES_DIR="/etc/httpd/conf.d"
APACHE_USER="apache"
USE_A2ENMOD=false
;;
*)
echo -e "${RED}[ERROR] Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}[ERROR] Cannot detect distribution${NC}"
exit 1
fi
echo -e "${GREEN}[1/8] Updating system packages...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/8] Installing Apache HTTP Server...${NC}"
if [ "$PKG_MGR" = "apt" ]; then
$PKG_INSTALL apache2 apache2-utils
else
$PKG_INSTALL httpd httpd-tools
fi
echo -e "${GREEN}[3/8] Enabling required Apache modules...${NC}"
if [ "$USE_A2ENMOD" = true ]; then
# Debian/Ubuntu systems
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_balancer
a2enmod lbmethod_byrequests
a2enmod cache
a2enmod cache_disk
a2enmod headers
a2enmod rewrite
else
# RHEL-based systems - modules are loaded via configuration
MAIN_CONFIG="${APACHE_CONFIG_DIR}/conf/httpd.conf"
# Check if modules are already loaded to avoid duplicates
if ! grep -q "LoadModule proxy_module" "$MAIN_CONFIG"; then
cat >> "$MAIN_CONFIG" << 'EOF'
# Proxy and Cache modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule headers_module modules/mod_headers.so
LoadModule rewrite_module modules/mod_rewrite.so
EOF
fi
fi
echo -e "${GREEN}[4/8] Creating cache directory...${NC}"
CACHE_DIR="/var/cache/${APACHE_SERVICE}/mod_cache_disk"
mkdir -p "$CACHE_DIR"
chown "$APACHE_USER:$APACHE_USER" "$CACHE_DIR"
chmod 755 "$CACHE_DIR"
echo -e "${GREEN}[5/8] Configuring Apache performance settings...${NC}"
MAIN_CONFIG_FILE="${APACHE_CONFIG_DIR}/apache2.conf"
if [ "$PKG_MGR" != "apt" ]; then
MAIN_CONFIG_FILE="${APACHE_CONFIG_DIR}/conf/httpd.conf"
fi
if ! grep -q "MaxRequestWorkers 400" "$MAIN_CONFIG_FILE"; then
cat >> "$MAIN_CONFIG_FILE" << 'EOF'
# Performance tuning for proxy workloads
MaxRequestWorkers 400
ThreadsPerChild 25
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
# Proxy timeout settings
ProxyTimeout 300
ProxyPreserveHost On
# Security headers
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy strict-origin-when-cross-origin
EOF
fi
echo -e "${GREEN}[6/8] Creating virtual host configuration...${NC}"
# Build proxy balancer configuration
BALANCER_CONFIG=""
BALANCER_MEMBERS=""
for i in "${!BACKEND_SERVICES[@]}"; do
service="${BACKEND_SERVICES[$i]}"
BALANCER_MEMBERS+=" BalancerMember http://${service}\\n"
done
# Create virtual host configuration
VHOST_CONFIG="${SITES_DIR}/${DOMAIN_NAME}.conf"
cat > "$VHOST_CONFIG" << EOF
<VirtualHost *:80>
ServerName ${DOMAIN_NAME}
DocumentRoot /var/www/html
# Enable caching
CacheEnable disk /
CacheRoot ${CACHE_DIR}
CacheDirLevels 2
CacheDirLength 1
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheMinExpire 60
CacheDetailHeader on
CacheHeader on
# Cache policies for different content types
<LocationMatch "\.(css|js|png|jpg|jpeg|gif|ico|svg)$">
CacheDefaultExpire 604800
ExpiresActive On
ExpiresDefault "access plus 1 week"
Header set Cache-Control "public, max-age=604800"
</LocationMatch>
<LocationMatch "\.(json|xml)$">
CacheDefaultExpire 300
ExpiresActive On
ExpiresDefault "access plus 5 minutes"
Header set Cache-Control "public, max-age=300"
</LocationMatch>
# Proxy balancer configuration
ProxyPreserveHost On
ProxyRequests Off
<Proxy balancer://microservices>
$(echo -e "$BALANCER_MEMBERS")
ProxySet lbmethod=byrequests
</Proxy>
# Route all requests to the balancer
ProxyPassMatch ^/(.*)$ balancer://microservices/\$1
ProxyPassReverse / balancer://microservices/
# Balancer manager (restrict access in production)
<Location "/balancer-manager">
SetHandler balancer-manager
Require local
</Location>
ProxyPass /balancer-manager !
# Logging
ErrorLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_error.log
CustomLog \${APACHE_LOG_DIR}/${DOMAIN_NAME}_access.log combined
</VirtualHost>
EOF
chmod 644 "$VHOST_CONFIG"
echo -e "${GREEN}[7/8] Enabling virtual host and starting Apache...${NC}"
if [ "$USE_A2ENMOD" = true ]; then
# Enable site on Debian/Ubuntu
a2ensite "${DOMAIN_NAME}.conf"
# Disable default site if it exists
if [ -f "/etc/apache2/sites-enabled/000-default.conf" ]; then
a2dissite 000-default
fi
fi
# Test Apache configuration
if "${APACHE_SERVICE}" -t; then
echo -e "${YELLOW}Apache configuration test passed${NC}"
else
echo -e "${RED}[ERROR] Apache configuration test failed${NC}"
exit 1
fi
# Start and enable Apache service
systemctl enable "$APACHE_SERVICE"
systemctl restart "$APACHE_SERVICE"
# Configure firewall if present
if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld; then
echo -e "${YELLOW}Configuring firewalld...${NC}"
firewall-cmd --permanent --add-service=http
firewall-cmd --reload
elif command -v ufw &> /dev/null; then
echo -e "${YELLOW}Configuring ufw...${NC}"
ufw --force enable
ufw allow 80/tcp
fi
echo -e "${GREEN}[8/8] Verifying installation...${NC}"
# Check if Apache is running
if systemctl is-active --quiet "$APACHE_SERVICE"; then
echo -e "${GREEN}✓ Apache is running${NC}"
else
echo -e "${RED}✗ Apache is not running${NC}"
exit 1
fi
# Check if cache directory exists and has correct permissions
if [ -d "$CACHE_DIR" ] && [ "$(stat -c %U:%G "$CACHE_DIR")" = "${APACHE_USER}:${APACHE_USER}" ]; then
echo -e "${GREEN}✓ Cache directory configured correctly${NC}"
else
echo -e "${RED}✗ Cache directory configuration failed${NC}"
exit 1
fi
# Check if virtual host file exists
if [ -f "$VHOST_CONFIG" ]; then
echo -e "${GREEN}✓ Virtual host configuration created${NC}"
else
echo -e "${RED}✗ Virtual host configuration missing${NC}"
exit 1
fi
echo -e "${GREEN}[SUCCESS] Apache reverse proxy with caching has been configured successfully!${NC}"
echo -e "${YELLOW}Configuration details:${NC}"
echo " - Domain: ${DOMAIN_NAME}"
echo " - Backend services: ${BACKEND_SERVICES[*]}"
echo " - Cache directory: ${CACHE_DIR}"
echo " - Virtual host config: ${VHOST_CONFIG}"
echo " - Balancer manager: http://${DOMAIN_NAME}/balancer-manager"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Update your DNS to point ${DOMAIN_NAME} to this server"
echo "2. Test the setup: curl -H 'Host: ${DOMAIN_NAME}' http://localhost/"
echo "3. Monitor logs: tail -f ${APACHE_LOG_DIR}/${DOMAIN_NAME}_access.log"
echo "4. Consider setting up SSL/TLS certificates for production use"
Review the script before running. Execute with: bash install.sh