Debian 13 Packaging & Clustering Series โ Part 10: Securing and Exposing the External API
In the previous parts of this series, we successfully packaged and deployed the Ring2All core databases, Kamailio SBCs, GlusterFS clusters, and RTPEngine media nodes. In this tenth installment, we will focus on exposing and securing the Ring2All Fastify API Backend for external integrations (such as CRMs, custom ERP systems, or notification bots).
By default, the Ring2All API backend listens locally on port 3000/3001. We will deploy a secure Nginx Reverse Proxy on the Portal/Admin nodes to expose the API over HTTPS (port 443) and write custom NFTables filtering rules (the native packet filter framework in Debian 13) to restrict API access to trusted external source IPs.
๐ ๏ธ Step 1: Configuring Nginx Secure Reverse Proxy
To proxy incoming external HTTPS requests to our internal API listener on port 3000, we configure Nginx with upstream proxy blocks and secure SSL/TLS protocols.
- Create a new Nginx virtual host configuration:
nano /etc/nginx/sites-available/ring2all-api.conf
- Add the following upstream and server block config:
upstream ring2all_api {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name pbx.softswitchone.com;
# SSL Certificates
ssl_certificate /etc/letsencrypt/live/pbx.softswitchone.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pbx.softswitchone.com/privkey.pem;
# Strong TLS configuration (Debian 13 default openssl 3.x)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
# Security Headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Logging
access_log /var/log/nginx/ring2all_api_access.log;
error_log /var/log/nginx/ring2all_api_error.log warn;
# Proxy to Fastify Backend API
location /api/ {
proxy_pass http://ring2all_api/;
proxy_http_version 1.1;
# Keep-alive headers
proxy_set_header Connection "";
# Real IP 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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Max upload limit (e.g. for CSV bulk provisioning imports)
client_max_body_size 100M;
}
}
- Enable the configuration and test Nginx:
ln -s /etc/nginx/sites-available/ring2all-api.conf /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx
๐ Step 2: Securing Access with NFTables (Debian 13 Native)
Debian 13 utilizes nftables as the default packet filtering framework, replacing the legacy iptables backend. We will define an address set representing authorized CRM and external developer IPs, allowing traffic to port 443 only from those addresses while dropping all other requests.
- Open the nftables configuration file:
nano /etc/nftables.conf
- Update the input chain rules to define a set of allowed external IPs. Replace the configuration with the following structure:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
# Set of authorized external IPs (CRMs, third-party developers, webhooks)
set authorized_api_ips {
type ipv4_addr
flags interval
elements = {
192.168.1.50, # Corporate Office IP
76.13.102.153, # Dev Server IP
104.16.0.0/12 # CRM Provider / HubSpot webhooks IP block example
}
}
chain input {
type filter hook input priority filter; policy drop;
# Allow loopback interface
iif "lo" accept
# Allow established/related state connections
ct state established,related accept
# Drop invalid connections
ct state invalid drop
# Allow SSH (Port 22) - Restrict to Admin subnet in production
tcp dport 22 accept
# Allow HTTP (Port 80) for Let's Encrypt SSL verification challenges
tcp dport 80 accept
# Allow HTTPS (Port 443) only from the authorized sets
ip saddr @authorized_api_ips tcp dport 443 accept
# Logging and dropping unauthorized packets
log prefix "NFTABLES-INPUT-BLOCKED: " flags all counter drop
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
- Validate and load the new nftables rules:
# Test rule compilation syntax
nft -c -f /etc/nftables.conf
# Reload service
systemctl restart nftables
systemctl enable nftables
๐งช Step 3: Verification
Once Nginx and NFTables are loaded on your Debian 13 portal server:
1. Test from an Unauthorized IP Address
Attempting to connect to the API from an IP not listed inside the authorized_api_ips set will block connection negotiations:
curl -I -v https://pbx.softswitchone.com/api/telephony/extensions
# Output should timeout or report "Connection refused/timed out"
2. Test from an Authorized IP Address
Connecting from a whitelisted IP with the correct API Key header will query the platform successfully:
curl -i -H "X-API-Key: ak_your_key" \
https://pbx.softswitchone.com/api/telephony/extensions
# Output: HTTP/1.1 200 OK
3. Check NFTables Block Logs
You can monitor blocked attempts in real-time in the system log:
journalctl -k -f | grep "NFTABLES-INPUT-BLOCKED"
This concludes Part 10 of our series. Exposing your Ring2All cluster to external CRM integrations is now completely locked down and secure.