This guide explains how to deploy the FlossWare Platform REST API with TLS/HTTPS encryption using reverse proxy solutions.
The platform's REST API currently uses JDK's HttpServer, which does not support TLS natively. Until native TLS support is implemented (see Issue #326), you must deploy with a reverse proxy in production environments to encrypt API traffic.
DO NOT expose the unencrypted HTTP endpoint to untrusted networks.
Client (HTTPS) → Reverse Proxy (TLS Termination) → Platform API (HTTP)
The reverse proxy:
- Terminates TLS connections from clients
- Forwards decrypted requests to the platform API on localhost
- Encrypts responses back to clients
- Handles certificate management
- Platform API running on
http://localhost:8080(or configured port) - TLS certificate and private key (see Certificate Management)
- Reverse proxy installed (nginx, Traefik, Caddy, or HAProxy)
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install nginx
# RHEL/CentOS/Fedora
sudo dnf install nginx
# macOS
brew install nginxCreate /etc/nginx/sites-available/platform-api:
# Redirect HTTP to HTTPS
server {
listen 80;
server_name api.example.com;
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server
server {
listen 443 ssl http2;
server_name api.example.com;
# TLS Configuration
ssl_certificate /etc/ssl/certs/platform-api.crt;
ssl_certificate_key /etc/ssl/private/platform-api.key;
# Modern TLS settings (Mozilla Intermediate)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
# HSTS (15768000 seconds = 6 months)
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
# 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;
# Proxy to platform API
location / {
proxy_pass http://127.0.0.1:8080;
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 https;
# WebSocket support (if needed)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check endpoint (bypass auth)
location /health {
proxy_pass http://127.0.0.1:8080/health;
access_log off;
}
}# Enable site
sudo ln -s /etc/nginx/sites-available/platform-api /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Restart nginx
sudo systemctl restart nginx
# Enable on boot
sudo systemctl enable nginx# Test HTTPS endpoint
curl -v https://api.example.com/api/applications
# Check TLS version
openssl s_client -connect api.example.com:443 -tls1_2
# Test with authenticated request
curl -H "X-API-Key: your-api-key" https://api.example.com/api/applicationsCreate docker-compose.yml:
version: '3.8'
services:
platform-api:
image: flossware/platform-java:latest
container_name: platform-api
networks:
- internal
environment:
- LOG_LEVEL=INFO
labels:
- "traefik.enable=true"
- "traefik.http.routers.platform-api.rule=Host(`api.example.com`)"
- "traefik.http.routers.platform-api.entrypoints=websecure"
- "traefik.http.routers.platform-api.tls=true"
- "traefik.http.routers.platform-api.tls.certresolver=letsencrypt"
- "traefik.http.services.platform-api.loadbalancer.server.port=8080"
traefik:
image: traefik:v2.10
container_name: traefik
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
networks:
- internal
networks:
internal:
driver: bridge# Create Let's Encrypt storage
mkdir letsencrypt
chmod 600 letsencrypt
# Start services
docker-compose up -d
# View logs
docker-compose logs -f traefik platform-apiCaddy automatically obtains and renews TLS certificates from Let's Encrypt.
# Ubuntu/Debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddyCreate /etc/caddy/Caddyfile:
api.example.com {
# Automatic HTTPS via Let's Encrypt
reverse_proxy localhost:8080
# Security headers
header {
Strict-Transport-Security "max-age=15768000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
}
# Logging
log {
output file /var/log/caddy/platform-api.log
format json
}
}# Start service
sudo systemctl start caddy
# Enable on boot
sudo systemctl enable caddy
# Check status
sudo systemctl status caddy# Ubuntu/Debian
sudo apt-get install haproxy
# RHEL/CentOS/Fedora
sudo dnf install haproxyEdit /etc/haproxy/haproxy.cfg:
global
log /dev/log local0
maxconn 2000
user haproxy
group haproxy
daemon
# TLS settings
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend platform_https
bind *:443 ssl crt /etc/ssl/private/platform-api.pem
mode http
# Security headers
http-response set-header Strict-Transport-Security "max-age=15768000; includeSubDomains"
http-response set-header X-Frame-Options "SAMEORIGIN"
default_backend platform_api
backend platform_api
mode http
balance roundrobin
option httpchk GET /health
http-check expect status 200
server api1 127.0.0.1:8080 checksudo systemctl restart haproxy
sudo systemctl enable haproxy# Install certbot
sudo apt-get install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d api.example.com
# Auto-renewal (already configured)
sudo systemctl status certbot.timerTraefik handles Let's Encrypt automatically (see Traefik config above).
Caddy handles ACME automatically (see Caddy config above).
# Generate private key
openssl genrsa -out platform-api.key 2048
# Generate certificate signing request
openssl req -new -key platform-api.key -out platform-api.csr \
-subj "/C=US/ST=State/L=City/O=Organization/CN=api.example.com"
# Generate self-signed certificate (valid 365 days)
openssl x509 -req -days 365 -in platform-api.csr \
-signkey platform-api.key -out platform-api.crt
# Install certificate
sudo cp platform-api.crt /etc/ssl/certs/
sudo cp platform-api.key /etc/ssl/private/
sudo chmod 600 /etc/ssl/private/platform-api.key- Purchase certificate from a CA (DigiCert, Sectigo, etc.)
- Generate CSR:
openssl req -new -newkey rsa:2048 -nodes -keyout platform-api.key -out platform-api.csr - Submit CSR to CA
- Download signed certificate
- Install certificate and key in
/etc/ssl/
Minimum: TLS 1.2
Recommended: TLS 1.2 and 1.3 only
ssl_protocols TLSv1.2 TLSv1.3;Follow Mozilla SSL Configuration Generator:
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';Force HTTPS for all future requests:
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;In ApiServerConfig:
ApiServerConfig config = ApiServerConfig.builder()
.bindAddress("127.0.0.1") // Only accept local connections
.port(8080)
.build();This prevents direct access to the unencrypted API.
# Allow HTTPS
sudo ufw allow 443/tcp
# Allow HTTP (for redirect)
sudo ufw allow 80/tcp
# Deny direct access to API port
sudo ufw deny 8080/tcp
# Enable firewall
sudo ufw enableFor public servers, use SSL Labs:
# Should achieve A or A+ rating# Test TLS 1.2
openssl s_client -connect api.example.com:443 -tls1_2
# Test TLS 1.3
openssl s_client -connect api.example.com:443 -tls1_3
# Test with SNI
openssl s_client -connect api.example.com:443 -servername api.example.com
# Check certificate expiration
openssl s_client -connect api.example.com:443 2>/dev/null | openssl x509 -noout -dates# Scan TLS configuration
nmap --script ssl-enum-ciphers -p 443 api.example.com
# Check for vulnerabilities
nmap --script ssl-* -p 443 api.example.comProblem: "Certificate not trusted"
Solution: Ensure certificate chain is complete:
ssl_certificate /etc/ssl/certs/platform-api-fullchain.crt; # Include intermediate certsProblem: Cannot connect to HTTPS endpoint
Check:
- Reverse proxy is running:
systemctl status nginx - Firewall allows 443:
sudo ufw status - Certificate files exist:
ls -l /etc/ssl/certs/platform-api.crt
Problem: Browser shows "mixed content" warnings
Solution: Ensure all resources use HTTPS:
add_header Content-Security-Policy "upgrade-insecure-requests" always;Phase 2 of Issue #326 will implement native TLS support using Netty or Undertow. The configuration will look like:
ApiServerConfig config = ApiServerConfig.builder()
.enableTls(true)
.keystorePath("/etc/platform/keystore.jks")
.keystorePassword("changeit")
.keyAlias("platform-api")
.build();Until then, reverse proxy deployment is the recommended approach.