Browser requests ──→ Local cache ──→ OS cache ──→ Router cache ──→ ISP DNS
│
Recursive query
│
┌───────────────┼───────────────┐
↓ ↓ ↓
Root NS TLD NS (.com) Authoritative NS
│ │ │
Referral Referral ANSWER (A/AAAA/CNAME)
@ IN A 1.2.3.4 |
| AAAA | IPv6 address | @ IN AAAA 2001:db8::1 |
| CNAME | Alias | www IN CNAME example.com |
| MX | Mail server | @ IN MX 10 mail.example.com |
| TXT | Text/spf/dkim | @ IN TXT "v=spf1 include:_spf.google.com ~all" |
| SRV | Service record | _sip._tcp IN SRV 10 60 5060 sip.example.com |
| NS | Name server | @ IN NS ns1.example.com |
| CAA | Certificate auth | @ IN CAA 0 issue "letsencrypt.org" |
# Query specific DNS server
dig @8.8.8.8 example.com A +short
dig @8.8.8.8 example.com MX +short
dig @8.8.8.8 example.com CNAME +short
Trace resolution path
dig +trace example.com
Reverse DNS lookup
dig -x 1.2.3.4
Check DNS propagation
dnsviz.net # Online tool
Or use multiple resolvers
for dns in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "=== $dns ===" && dig @$dns example.com A +short
done
Flush local DNS cache
systemd-resolve --flush-caches # systemd-resolved
rndc flush # BIND
Need a reverse proxy? ─┬─ HTTP routing + SSL ──→ Nginx (most common)
├─ API gateway + rate limiting ──→ Kong / Traefik
├─ TCP/UDP load balancing ──→ HAProxy
└─ Service mesh (microservices) ──→ Envoy + Istio
# /etc/nginx/sites-available/api.myapp.com
upstream api_backend {
least_conn;
server 10.0.1.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.3:8080 backup;
keepalive 32;
}
Rate limiting zone
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
server {
listen 443 ssl http2;
server_name api.myapp.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/api.myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.myapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/api.myapp.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# HSTS (31536000s = 1 year)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Logging
access_log /var/log/nginx/api_access.log json_combined;
error_log /var/log/nginx/api_error.log warn;
location / {
limit_req zone=api burst=200 nodelay;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
location /auth/ {
limit_req zone=auth burst=10 nodelay;
proxy_pass http://api_backend;
# ... same proxy headers
}
# Health check
location /health {
proxy_pass http://api_backend/health;
access_log off;
}
}
HTTP → HTTPS redirect
server {
listen 80;
server_name api.myapp.com;
return 301 https://$host$request_uri;
}
frontend mysql_front
bind *:3306
default_backend mysql_back
backend mysql_back
balance leastconn
option mysql-check user haproxy_check
server mysql1 10.0.1.1:3306 check inter 5s rise 3 fall 2
server mysql2 10.0.1.2:3306 check inter 5s rise 3 fall 2 backup
Client Server
│─── ClientHello (supported versions, ──→│
│ cipher suites, key_share) │
│ │
│←── ServerHello (chosen cipher, │
│ key_share, certificate) ────│
│ │
│─── Finished (encrypted) ──────────────────→│
│←── Finished (encrypted) ──────────────────│
│ │
│════════ Encrypted Application Data ════════│
# Install certbot
apt install certbot python3-certbot-nginx
Get certificate (standalone)
certbot certonly --standalone -d api.myapp.com --non-interactive --agree-tos -m admin@myapp.com
Get certificate (dns challenge for wildcard)
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d '*.myapp.com' -d 'myapp.com'
Auto-renewal (already set up by certbot timer)
certbot renew --dry-run # Test renewal
Check certificate info
openssl x509 -in /etc/letsencrypt/live/api.myapp.com/fullchain.pem -noout -text | grep -E 'Subject:|Not Before|Not After'
# /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
Issue wildcard cert
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d '*.hkuk2.s4s.host' -d 'hkuk2.s4s.host' \
--non-interactive --agree-tos -m admin@s4s.host
# Create private key + certificate
openssl req -x509 -newkey rsa:4096 \
-keyout internal.key -out internal.crt \
-days 365 -nodes \
-subj "/CN=internal.myapp.com/O=MyApp/C=US" \
-addext "subjectAltName=DNS:internal.myapp.com,DNS:*.internal.myapp.com"
Create CA and sign certificate (for internal PKI)
1. Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=MyApp Internal CA/O=MyApp/C=US"
2. Create CSR
openssl genrsa -out service.key 2048
openssl req -new -key service.key -out service.csr \
-subj "/CN=api.internal.myapp.com/O=MyApp/C=US"
3. Sign with CA
openssl x509 -req -days 365 -in service.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out service.crt \
-extfile <(echo "subjectAltName=DNS:api.internal.myapp.com")
# Cloudflare API: Set SSL mode to "Full (Strict)"
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/ssl" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"value":"strict"}'
Cache rules for API
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/pagerules" \
-H "Authorization: Bearer ${CF_TOKEN}" \
--data '{
"targets": [{"target":"url","constraint":{"operator":"matches","value":"api.myapp.com/v1/*"}}],
"actions": [{"id":"cache_level","value":"bypass"}],
"priority": 1
}'
Purge cache
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CF_TOKEN}" \
--data '{"purge_everything":true}'
# Real IP restoration behind Cloudflare
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
real_ip_header CF-Connecting-IP;
# Default policies
ufw default deny incoming
ufw default allow outgoing
Allow SSH (from specific IPs only)
ufw allow from 10.0.1.0/24 to any port 22 proto tcp
ufw allow from 203.0.113.50 to any port 22 proto tcp # Office IP
Allow web
ufw allow 80/tcp
ufw allow 443/tcp
Allow specific services
ufw allow from 10.0.1.0/24 to any port 3306 proto tcp # MySQL from app servers
ufw allow from 10.0.1.0/24 to any port 5432 proto tcp # PostgreSQL from app servers
ufw allow from 10.0.1.0/24 to any port 6379 proto tcp # Redis from app servers
Rate limit SSH
ufw limit 22/tcp
Enable
ufw enable
ufw status verbose
# Block common attacks
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --rttl --name SSH -j DROP
SYN flood protection
iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
Block known bad IPs
iptables -A INPUT -s 1.2.3.4 -j DROP
Save rules
iptables-save > /etc/iptables/rules.v4
SSL problem? ─┬─ Certificate expired ──→ Auto-renew with certbot, check cron timer
├─ Certificate not trusted ──→ Install full chain (cert + intermediate)
├─ Mixed content ──→ Replace all http:// with https:// in HTML/JS
├─ HSTS issues ──→ Check preload status, clear browser HSTS cache
├─ SNI not configured ──→ Add server_name in nginx, check ssl_preread
├─ Weak ciphers ──→ Update ssl_ciphers to Mozilla Modern profile
└─ OCSP error ──→ Enable ssl_stapling, check resolver in nginx
DNS not resolving? ─┬─ NXDOMAIN ──→ Check DNS records exist at authoritative NS
├─ SERVFAIL ──→ Check DNSSEC, nameserver connectivity
├─ Timeout ──→ Check firewall allows UDP/TCP 53
├─ Wrong answer ──→ Check TTL expired, flush caches
└─ Works from some locations ──→ Propagation delay (wait for TTL)
Can't connect? ─┬─ ping fails ──→ Firewall, routing, host down
├─ ping works, port fails ──→ Service not listening, firewall
├─ Connection refused ──→ Service not running or wrong port
├─ Connection timeout ──→ Firewall DROP, routing issue
├─ Connection reset ──→ Service crashed, IDS/IPS blocking
└─ Works from localhost ──→ Firewall/iptables rule blocking external
# Test SSL configuration (Qualys SSL Labs alternative)
Using testssl.sh
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh -E https://api.myapp.com
Quick SSL check with openssl
openssl s_client -connect api.myapp.com:443 -servername api.myapp.com /dev/null | openssl x509 -noout -dates -subject -issuer
Check specific TLS version
openssl s_client -connect api.myapp.com:443 -tls1_3 /dev/null | grep "Protocol"
DNS resolution test across providers
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "=== $resolver ===" && dig @$resolver api.myapp.com A +short
done
# Bandwidth test with iperf3
iperf3 -s # On server
iperf3 -c server-ip -t 30 -P 4 # On client (4 parallel streams, 30s)
Latency test
ping -c 100 api.myapp.com | tail -1
MTR (combined traceroute + ping)
mtr --report api.myapp.com
TCP connection stats
ss -s # Summary
ss -tlp # Listening TCP sockets
ss -s | grep -E 'estab|timewait'
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rslimit_nofile 100000;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Keep-alive
keepalive_timeout 65;
keepalive_requests 100000;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 4;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml;
# Buffer sizes
client_body_buffer_size 16K;
client_header_buffer_size 1k;
client_max_body_size 64m;
large_client_header_buffers 4 8k;
# File cache
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Upstream keepalive
upstream api_backend {
server 10.0.1.1:8080;
keepalive 32;
}
}
# Use local DNS cache (systemd-resolved)
/etc/systemd/resolved.conf
[Resolve]
DNS=8.8.8.8 1.1.1.1
FallbackDNS=9.9.9.9
Cache=yes
CacheFromLocal=yes
DNSOverTLS=opportunistic
# Verify full trust chain
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
-untrusted /etc/letsencrypt/live/api.myapp.com/chain.pem \
/etc/letsencrypt/live/api.myapp.com/cert.pem
Check certificate SANs (critical for multi-domain certs)
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name"
Generate DH params (for DHE cipher suites — use ECDHE instead when possible)
openssl dhparam -out /etc/nginx/dhparam.pem 2048
#!/bin/bash
/usr/local/bin/check-certs.sh — alert 30 days before expiry
ALERT_DAYS=30
for cert in /etc/letsencrypt/live/*/fullchain.pem; do
domain=$(basename $(dirname $cert))
expiry=$(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2)
days_left=$(( ( $(date -d "$expiry" +%s) - $(date +%s) ) / 86400 ))
if [ "$days_left" -lt "$ALERT_DAYS" ]; then
echo "⚠️ Certificate for $domain expires in $days_left days!" | \
mail -s "CERT EXPIRY: $domain" admin@myapp.com
fi
done
#!/bin/bash
/usr/local/bin/blue-green-switch.sh
ENV=$1 # "blue" or "green"
Health check target first
curl -sf http://10.0.1.$([ "$ENV" = "blue" ] && echo "1" || echo "2"):8080/health || {
echo "❌ $ENV environment health check failed!"; exit 1
}
Update upstream in nginx
sed -i "s/server 10.0.1.[12]/server 10.0.1.$([ "$ENV" = "blue" ] && echo "1" || echo "2")/" \
/etc/nginx/conf.d/upstream.conf
nginx -t && nginx -s reload
echo "✅ Switched to $ENV environment"
#!/bin/bash
/etc/letsencrypt/renewal-hooks/deploy/reload-services.sh
set -e
Reload nginx to pick up new certs
systemctl reload nginx
Reload any other services using certs
systemctl reload postfix 2>/dev/null || true
systemctl reload dovecot 2>/null || true
Send notification
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d '{"text":"✅ SSL certificate renewed and services reloaded"}'
# pt-online-schema-change handles this for MySQL
For PostgreSQL: use CREATE + INSERT + RENAME pattern
Step 1: Create new table alongside old
psql -c "CREATE TABLE orders_v2 (LIKE orders INCLUDING ALL);"
Step 2: Add new columns to v2
psql -c "ALTER TABLE orders_v2 ADD COLUMN priority INTEGER DEFAULT 0;"
Step 3: Backfill in batches (avoid locking)
for offset in $(seq 0 10000 $(psql -t -c "SELECT COUNT(*) FROM orders")); do
psql -c "INSERT INTO orders_v2 SELECT *, 0 FROM orders ORDER BY id OFFSET $offset LIMIT 10000 ON CONFLICT DO NOTHING;"
sleep 1 # Let replication catch up
done
Step 4: Swap tables in a transaction
psql -c "BEGIN; ALTER TABLE orders RENAME TO orders_old; ALTER TABLE orders_v2 RENAME TO orders; COMMIT;"
Step 5: Drop old table after verification
psql -c "DROP TABLE orders_old;"
net.ipv4.tcp_fastopen=3 on both client and server| # | Pitfall | Impact | Fix |
|---|---------|--------|-----|
| 1 | Missing intermediate cert | Browser shows untrusted | Use fullchain.pem, not cert.pem |
| 2 | Not restoring real IP | All traffic appears from proxy | Set real_ip_header CF-Connecting-IP |
| 3 | HTTP→HTTPS redirect loop | Cloudflare "too many redirects" | Set SSL mode to Full (Strict) |
| 4 | DNS TTL too high | Can't failover quickly | Set TTL to 300-300s for critical records |
| 5 | Not rate-limiting SSH | Brute force attacks | ufw limit 22/tcp or fail2ban |
| 6 | Mixed content warnings | Browser blocks resources | Replace all http:// in HTML with https:// |
| 7 | Weak DH parameters | Logjam attack | Use 2048-bit+ DH params or ECDHE only |
| 8 | Not monitoring cert expiry | Service goes down | certbot renew --dry-run + monitoring |
| 9 | DNS cache poisoning | Wrong IP responses | Enable DNSSEC, use trusted resolvers |
| 10 | Proxy buffers too small | 502 errors on large responses | Increase proxy_buffer_size to 16k |
# Check SSL cert details
openssl x509 -in /etc/letsencrypt/live/domain/fullchain.pem -noout -dates -issuer
Test TLS version and ciphers
openssl s_client -connect domain:443 -tls1_3 /dev/null | grep "Cipher"
Nginx config test and reload
nginx -t && nginx -s reload
Firewall: check rules
ufw status numbered
iptables -L -n -v --line-numbers
DNS: check propagation
dig domain A +short @8.8.8.8
Cloudflare: purge cache
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"purge_everything":true}'
Network: check connections
ss -tlnp | grep nginx # Listening ports
ss -s # Connection summary