Building a truly active-active architecture across continents requires careful orchestration of DNS routing, load balancing, database replication, and object storage synchronization. In this comprehensive tutorial, we’ll deploy a production-ready active-active setup between Onidel Amsterdam VPS and Onidel New York VPS instances, leveraging GeoDNS for intelligent traffic routing, HAProxy for load balancing, PostgreSQL 17’s enhanced logical replication for bidirectional database synchronization, and MinIO for S3-compatible object storage replication.
Why Active-Active Architecture Matters
Traditional active-passive setups leave resources idle and introduce failover delays. Active-active architectures maximize resource utilization while providing zero-downtime resilience and improved user experience through geographic proximity. This setup is particularly valuable for applications serving both European and North American markets, where Amsterdam VPS vs New York VPS latency differences can significantly impact performance.
Prerequisites
Before beginning this tutorial, ensure you have:
- Two Ubuntu 24.04 LTS VPS instances: One in Amsterdam and one in New York (minimum 4GB RAM, 2 vCPUs each)
- Domain name with DNS provider supporting GeoDNS or Anycast (Cloudflare, Route 53, NS1)
- Root access on both servers
- SSL certificates for your domain
- S3-compatible storage buckets in both regions
Architecture Overview
Our active-active setup consists of:
- GeoDNS/Anycast: Routes users to nearest region
- HAProxy: Load balancing and health checks
- PostgreSQL 17: Bidirectional logical replication
- MinIO: Cross-region object storage replication
- Application servers: Containerized web services
Step 1: System Preparation
First, update both servers and install required packages:
# On both Amsterdam and New York VPS
sudo apt update && sudo apt upgrade -y
sudo apt install -y haproxy postgresql-17 postgresql-client-17 docker.io docker-compose-v2
# Configure system limits
echo "* soft nofile 65536" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 65536" | sudo tee -a /etc/security/limits.conf
# Enable IP forwarding for HAProxy
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Step 2: GeoDNS/Anycast Setup
Configure DNS routing using Cloudflare’s GeoDNS. Create A records for your domain:
# Amsterdam VPS record (Europe traffic)
Type: A
Name: @
IPv4: YOUR_AMSTERDAM_IP
TTL: 60 seconds
Region: Europe
# New York VPS record (Americas traffic)
Type: A
Name: @
IPv4: YOUR_NEWYORK_IP
TTL: 60 seconds
Region: Americas
For health-based failover, enable Cloudflare Health Checks. This ensures traffic automatically routes to the healthy region if one becomes unavailable.
Step 3: HAProxy Load Balancer Configuration
Configure HAProxy on both servers. Create /etc/haproxy/haproxy.cfg:
global
daemon
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660
stats timeout 30s
user haproxy
group haproxy
# TLS configuration
ssl-default-bind-ciphers ECDHE+AESGCM:ECDHE+CHACHA20:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
mode http
timeout connect 5000
timeout client 50000
timeout server 50000
compression algo gzip
compression type text/html text/plain text/css text/javascript application/javascript
frontend web_frontend
bind *:80
bind *:443 ssl crt /etc/ssl/private/yourdomain.pem
redirect scheme https if !{ ssl_fc }
# Health check endpoint
acl is_health_check path /health
http-request return status 200 content-type "text/plain" string "OK" if is_health_check
default_backend web_servers
backend web_servers
balance roundrobin
option httpchk GET /health
# Local application servers
server app1 127.0.0.1:8080 check
server app2 127.0.0.1:8081 check
# Cross-region failover (adjust IPs)
server remote_app1 REMOTE_VPS_IP:8080 check backup
server remote_app2 REMOTE_VPS_IP:8081 check backup
Start and enable HAProxy:
sudo systemctl enable haproxy
sudo systemctl start haproxy
Step 4: PostgreSQL 17 Logical Replication
PostgreSQL 17 introduces enhanced logical replication capabilities. Configure bidirectional replication between both regions.
Database Initialization
On both servers, initialize PostgreSQL:
# Initialize cluster
sudo pg_createcluster 17 main
sudo systemctl start postgresql@17-main
sudo systemctl enable postgresql@17-main
# Configure PostgreSQL
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'your_secure_password';"
sudo -u postgres createdb appdb
Replication Configuration
Edit /etc/postgresql/17/main/postgresql.conf on both servers:
# Replication settings
wal_level = logical
max_wal_senders = 4
max_replication_slots = 4
max_logical_replication_workers = 4
max_worker_processes = 8
# Performance tuning
shared_preload_libraries = 'pg_logical'
log_logical_decoding_work_mem = 64MB
Configure /etc/postgresql/17/main/pg_hba.conf:
# Replication connections
host replication postgres AMSTERDAM_IP/32 md5
host replication postgres NEWYORK_IP/32 md5
host appdb postgres AMSTERDAM_IP/32 md5
host appdb postgres NEWYORK_IP/32 md5
Logical Replication Setup
# Amsterdam VPS: Create publication
sudo -u postgres psql -d appdb -c "CREATE PUBLICATION amsterdam_pub FOR ALL TABLES;"
# New York VPS: Create publication
sudo -u postgres psql -d appdb -c "CREATE PUBLICATION newyork_pub FOR ALL TABLES;"
# Amsterdam VPS: Subscribe to New York
sudo -u postgres psql -d appdb -c "
CREATE SUBSCRIPTION newyork_sub
CONNECTION 'host=NEWYORK_IP dbname=appdb user=postgres password=your_secure_password'
PUBLICATION newyork_pub;"
# New York VPS: Subscribe to Amsterdam
sudo -u postgres psql -d appdb -c "
CREATE SUBSCRIPTION amsterdam_sub
CONNECTION 'host=AMSTERDAM_IP dbname=appdb user=postgres password=your_secure_password'
PUBLICATION amsterdam_pub;"
Step 5: S3 Object Storage Replication
Deploy MinIO for S3-compatible object storage with cross-region replication:
# docker-compose.yml for MinIO
version: '3.8'
services:
minio:
image: minio/minio:RELEASE.2024-01-16T16-07-38Z
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: your_secure_password
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
volumes:
minio_data:
Configure bucket replication using MinIO client:
# Install MinIO client
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# Configure aliases
mc alias set amsterdam http://AMSTERDAM_IP:9000 admin your_secure_password
mc alias set newyork http://NEWYORK_IP:9000 admin your_secure_password
# Create buckets
mc mb amsterdam/app-assets
mc mb newyork/app-assets
# Enable versioning
mc version enable amsterdam/app-assets
mc version enable newyork/app-assets
# Configure replication
mc replicate add amsterdam/app-assets --remote-bucket newyork/app-assets
mc replicate add newyork/app-assets --remote-bucket amsterdam/app-assets
Step 6: Application Deployment
Deploy your application stack with Docker Compose. Ensure applications are database-region-aware to avoid write conflicts:
# Application docker-compose.yml
version: '3.8'
services:
app1:
image: your-app:latest
ports:
- "8080:8080"
environment:
- DB_HOST=localhost
- DB_NAME=appdb
- REGION=amsterdam # or newyork
- S3_ENDPOINT=http://localhost:9000
depends_on:
- postgres
app2:
image: your-app:latest
ports:
- "8081:8081"
environment:
- DB_HOST=localhost
- DB_NAME=appdb
- REGION=amsterdam # or newyork
- S3_ENDPOINT=http://localhost:9000
Best Practices
- Conflict Resolution: Implement application-level conflict resolution for write operations
- Regional Data Affinity: Use region-specific primary keys (e.g., AMS_001, NYC_001 prefixes)
- Monitoring: Deploy comprehensive monitoring with observability stack
- Backup Strategy: Implement point-in-time recovery backups for both regions
- Security: Enable TLS for all inter-region communications
- Health Checks: Configure comprehensive health monitoring for automatic failover
Warning: Never perform destructive operations without testing in a staging environment first. Bidirectional replication can amplify mistakes across both regions.
Conclusion
You’ve successfully built a production-ready active-active architecture spanning Amsterdam and New York regions. This setup provides zero-downtime resilience, optimal user experience through geographic proximity, and efficient resource utilization. The combination of GeoDNS routing, HAProxy load balancing, PostgreSQL 17 logical replication, and MinIO object storage creates a robust foundation for global applications.
Ready to deploy your own active-active architecture? Explore our high-performance Amsterdam VPS and New York VPS options, featuring EPYC Milan processors, NVMe storage, and advanced networking capabilities perfect for demanding multi-region deployments.




