Skip to content

Security & SSL Configuration

Comprehensive guide to securing your self-hosted ANTE ERP installation.

Security Overview

A secure ANTE ERP installation requires:

  1. SSL/HTTPS - Encrypted communications
  2. Strong Passwords - All services and accounts
  3. Firewall Configuration - Restricted access
  4. Regular Updates - Security patches
  5. Access Control - User permissions
  6. Audit Logging - Activity monitoring

SSL/HTTPS Setup

This is the most common and recommended setup for production.

Step 1: Install Nginx

bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y nginx

# CentOS/RHEL
sudo yum install -y nginx

# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx

Step 2: Install Certbot

bash
# Ubuntu/Debian
sudo apt-get install -y certbot python3-certbot-nginx

# CentOS/RHEL
sudo yum install -y certbot python3-certbot-nginx

Step 3: Configure Domain DNS

Ensure your domain points to your server:

bash
# Check DNS resolution
dig +short erp.yourcompany.com

# Should return your server's IP address

Step 4: Create Nginx Configuration

Create /etc/nginx/sites-available/ante-erp:

nginx
# HTTP - Redirect to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name erp.yourcompany.com;

    # Let's Encrypt challenge
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Redirect all other traffic to HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS - Main configuration
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name erp.yourcompany.com;

    # SSL Certificates (will be configured by certbot)
    ssl_certificate /etc/letsencrypt/live/erp.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.yourcompany.com/privkey.pem;

    # SSL Configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    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 "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: https:; font-src 'self' data:;" always;

    # Max upload size
    client_max_body_size 100M;
    client_body_timeout 300s;

    # Frontend
    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
        
        # Timeouts
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
    }

    # Backend API
    location /api/ {
        proxy_pass http://localhost:3001/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
        
        # Timeouts
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
    }

    # WebSocket
    location /socket.io/ {
        proxy_pass http://localhost:4001/socket.io/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        
        # WebSocket timeouts
        proxy_connect_timeout 7d;
        proxy_send_timeout 7d;
        proxy_read_timeout 7d;
    }

    # Health check endpoint
    location /health {
        proxy_pass http://localhost:3001/health;
        access_log off;
    }

    # Static file caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://localhost:8080;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Logs
    access_log /var/log/nginx/ante-erp-access.log;
    error_log /var/log/nginx/ante-erp-error.log;
}

Step 5: Enable Configuration

bash
# Create symlink
sudo ln -s /etc/nginx/sites-available/ante-erp /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# If test passes, reload Nginx
sudo systemctl reload nginx

Step 6: Obtain SSL Certificate

bash
# Get certificate
sudo certbot --nginx -d erp.yourcompany.com

# Follow prompts:
# - Enter email address
# - Agree to terms
# - Choose redirect option (option 2)

# Certbot will automatically update your Nginx configuration

Step 7: Test Auto-Renewal

bash
# Test renewal process
sudo certbot renew --dry-run

# Certbot automatically sets up a systemd timer for renewal
# Verify it's active:
sudo systemctl status certbot.timer

Step 8: Update ANTE Configuration

Update your .env file:

bash
FRONTEND_URL=https://erp.yourcompany.com
API_URL=https://erp.yourcompany.com/api
SOCKET_URL=wss://erp.yourcompany.com/socket.io

Restart services:

bash
docker compose restart backend frontend

Option 2: Docker with SSL Termination

Use Nginx in Docker with automatic SSL.

Create docker-compose.ssl.yml:

yaml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    container_name: ante-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - certbot_data:/var/www/certbot:ro
      - certbot_conf:/etc/letsencrypt:ro
    depends_on:
      - frontend
      - backend
    networks:
      - ante-network

  certbot:
    image: certbot/certbot:latest
    container_name: ante-certbot
    volumes:
      - certbot_data:/var/www/certbot
      - certbot_conf:/etc/letsencrypt
    command: certonly --webroot --webroot-path=/var/www/certbot --email admin@yourcompany.com --agree-tos --no-eff-email -d erp.yourcompany.com

volumes:
  certbot_data:
  certbot_conf:

Firewall Configuration

UFW (Ubuntu/Debian)

bash
# Install UFW
sudo apt-get install ufw

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (IMPORTANT - do this first!)
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# If not using reverse proxy, allow direct access
# sudo ufw allow 8080/tcp  # Frontend
# sudo ufw allow 3001/tcp  # Backend
# sudo ufw allow 4001/tcp  # WebSocket

# Enable firewall
sudo ufw enable

# Check status
sudo ufw status verbose

Firewalld (CentOS/RHEL)

bash
# Install firewalld
sudo yum install firewalld

# Start and enable
sudo systemctl start firewalld
sudo systemctl enable firewalld

# Allow HTTP and HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Allow SSH
sudo firewall-cmd --permanent --add-service=ssh

# Reload firewall
sudo firewall-cmd --reload

# Check status
sudo firewall-cmd --list-all

Docker and Firewall

Docker can bypass UFW rules. To fix this:

bash
# Edit Docker daemon config
sudo nano /etc/docker/daemon.json

Add:

json
{
  "iptables": false
}

Restart Docker:

bash
sudo systemctl restart docker

Password Security

Generate Strong Passwords

bash
# Generate passwords for .env file
echo "DB_PASSWORD=$(openssl rand -base64 32)"
echo "REDIS_PASSWORD=$(openssl rand -base64 32)"
echo "MONGO_PASSWORD=$(openssl rand -base64 32)"
echo "JWT_SECRET=$(openssl rand -base64 64)"

Password Policy Configuration

In .env:

bash
# Enforce strong passwords
PASSWORD_MIN_LENGTH=12
PASSWORD_REQUIRE_UPPERCASE=true
PASSWORD_REQUIRE_LOWERCASE=true
PASSWORD_REQUIRE_NUMBER=true
PASSWORD_REQUIRE_SPECIAL=true
PASSWORD_MAX_AGE_DAYS=90

# Prevent password reuse
PASSWORD_HISTORY_COUNT=5

# Account lockout
MAX_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30

Access Control

Database Security

PostgreSQL

sql
-- Create read-only user for reporting
CREATE USER readonly WITH PASSWORD 'strong_password';
GRANT CONNECT ON DATABASE ante_db TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;

-- Revoke unnecessary permissions
REVOKE ALL ON SCHEMA public FROM PUBLIC;

MongoDB

javascript
// Create read-only user
db.createUser({
  user: "readonly",
  pwd: "strong_password",
  roles: [{ role: "read", db: "ante" }]
})

// Create backup user
db.createUser({
  user: "backup",
  pwd: "strong_password",
  roles: [{ role: "backup", db: "admin" }]
})

Network Security

Restrict Database Access

In docker-compose.yml, don't expose database ports:

yaml
services:
  postgres:
    # Remove or comment out ports section
    # ports:
    #   - "5433:5432"
    
    # Database only accessible within Docker network

If you must expose ports, bind to localhost only:

yaml
services:
  postgres:
    ports:
      - "127.0.0.1:5433:5432"  # Only accessible from localhost

Use Docker Networks

Isolate services:

yaml
networks:
  frontend-network:
    driver: bridge
  backend-network:
    driver: bridge
  database-network:
    driver: bridge

services:
  frontend:
    networks:
      - frontend-network
  
  backend:
    networks:
      - frontend-network
      - backend-network
      - database-network
  
  postgres:
    networks:
      - database-network

Security Headers

Already configured in Nginx above, but for reference:

nginx
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;

# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# Enable XSS protection
add_header X-XSS-Protection "1; mode=block" always;

# HTTP Strict Transport Security
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# Referrer Policy
add_header Referrer-Policy "no-referrer-when-downgrade" always;

# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

Rate Limiting

Nginx Rate Limiting

Add to Nginx configuration:

nginx
# Define rate limit zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

server {
    # Apply rate limiting
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        # ... rest of config
    }

    location /api/auth/login {
        limit_req zone=login_limit burst=3 nodelay;
        # ... rest of config
    }
}

Application-Level Rate Limiting

Configure in .env:

bash
RATE_LIMIT_ENABLED=true
RATE_LIMIT_WINDOW=900000  # 15 minutes
RATE_LIMIT_MAX_REQUESTS=100

Audit Logging

Enable Audit Logs

In .env:

bash
AUDIT_LOG_ENABLED=true
AUDIT_LOG_LEVEL=info
AUDIT_LOG_FILE=/app/logs/audit.log

# Events to log
AUDIT_LOG_AUTH=true
AUDIT_LOG_DATA_CHANGES=true
AUDIT_LOG_ADMIN_ACTIONS=true
AUDIT_LOG_FILE_ACCESS=true

View Audit Logs

bash
# View recent audit events
docker compose exec backend tail -f /app/logs/audit.log

# Search for specific user activity
docker compose exec backend grep "user:john.doe" /app/logs/audit.log

# View failed login attempts
docker compose exec backend grep "LOGIN_FAILED" /app/logs/audit.log

Security Scanning

Scan Docker Images

bash
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scan images
trivy image ghcr.io/gtplusnet/ante-self-hosted-backend:latest
trivy image ghcr.io/gtplusnet/ante-self-hosted-frontend-main:latest

# Scan for critical vulnerabilities only
trivy image --severity CRITICAL ghcr.io/gtplusnet/ante-self-hosted-backend:latest

Regular Security Updates

bash
# Update system packages
sudo apt-get update && sudo apt-get upgrade -y

# Update Docker images
docker compose pull
docker compose up -d

# Restart containers with new images
docker compose restart

Backup Encryption

Encrypt sensitive backups:

bash
# Encrypt backup
gpg --symmetric --cipher-algo AES256 backup_20251027.sql.gz

# Decrypt backup
gpg --decrypt backup_20251027.sql.gz.gpg > backup_20251027.sql.gz

Security Checklist

Before going to production:

  • [ ] SSL/HTTPS configured and tested
  • [ ] All default passwords changed
  • [ ] Strong passwords for all services
  • [ ] Firewall configured and enabled
  • [ ] Database ports not exposed publicly
  • [ ] Security headers configured
  • [ ] Rate limiting enabled
  • [ ] Audit logging enabled
  • [ ] Automated backups configured
  • [ ] Backup encryption enabled
  • [ ] SSL certificate auto-renewal tested
  • [ ] Security scanning performed
  • [ ] Access control policies defined
  • [ ] Admin users use strong passwords
  • [ ] Two-factor authentication considered
  • [ ] Regular update schedule established

Security Best Practices

Regular Maintenance

  1. Weekly:

    • Review audit logs
    • Check for failed login attempts
    • Monitor disk space
  2. Monthly:

    • Update Docker images
    • Review user access
    • Test backup restore
    • Scan for vulnerabilities
  3. Quarterly:

    • Rotate passwords and keys
    • Review security policies
    • Update SSL certificates (automatic with Let's Encrypt)
    • Security audit

Incident Response

If you detect a security incident:

  1. Isolate: Stop affected services

    bash
    docker compose stop backend frontend
  2. Investigate: Check logs

    bash
    docker compose logs backend > incident_logs.txt
  3. Backup: Save current state

    bash
    docker compose exec postgres pg_dump -U ante ante_db > incident_backup.sql
  4. Remediate: Fix vulnerability

  5. Restore: Bring services back online

  6. Monitor: Watch for further issues

Next Steps


Last Updated: October 27, 2025

Released under the MIT License.