feat: complete nginx migration from caddy
- Replace Caddy reverse proxy with Nginx (nginx:alpine) - Add nginx.conf with HTTP/HTTPS, gzip, and proxy settings - Add nginx-custom-domains.conf template for custom domains - Update docker-compose.yml to use Nginx service - Add ProxyFix middleware to Flask app for proper header handling - Create nginx_config_reader.py utility to read Nginx configuration - Update admin blueprint to display Nginx status in https_config page - Add Nginx configuration display to https_config.html template - Generate self-signed SSL certificates for localhost - Add utility scripts: generate_nginx_certs.sh - Add documentation: NGINX_SETUP_QUICK.md, PROXY_FIX_SETUP.md - All containers now running, HTTPS working, HTTP redirects to HTTPS - Session cookies marked as Secure - Security headers properly configured
This commit is contained in:
84
NGINX_SETUP_QUICK.md
Normal file
84
NGINX_SETUP_QUICK.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Quick Start: Nginx Setup for DigiServer v2
|
||||
|
||||
## Pre-requisites
|
||||
- SSL certificates in `./data/nginx-ssl/cert.pem` and `./data/nginx-ssl/key.pem`
|
||||
- Docker and Docker Compose installed
|
||||
- Port 80 and 443 available
|
||||
|
||||
## Quick Setup (3 steps)
|
||||
|
||||
### 1. Generate Self-Signed Certificates
|
||||
```bash
|
||||
./generate_nginx_certs.sh localhost 365
|
||||
```
|
||||
|
||||
### 2. Update Nginx Configuration
|
||||
- Edit `nginx.conf` to set your domain:
|
||||
```nginx
|
||||
server_name localhost; # Change to your domain
|
||||
```
|
||||
|
||||
### 3. Start Docker Compose
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Check if Nginx is running
|
||||
```bash
|
||||
docker ps | grep nginx
|
||||
```
|
||||
|
||||
### Test HTTP → HTTPS redirect
|
||||
```bash
|
||||
curl -L http://localhost
|
||||
```
|
||||
|
||||
### Test HTTPS (with self-signed cert)
|
||||
```bash
|
||||
curl -k https://localhost
|
||||
```
|
||||
|
||||
### View logs
|
||||
```bash
|
||||
docker logs digiserver-nginx
|
||||
docker exec digiserver-nginx tail -f /var/log/nginx/access.log
|
||||
```
|
||||
|
||||
## Using Production Certificates
|
||||
|
||||
### Option A: Let's Encrypt (Free)
|
||||
1. Install certbot: `apt-get install certbot`
|
||||
2. Generate cert: `certbot certonly --standalone -d your-domain.com`
|
||||
3. Copy cert: `cp /etc/letsencrypt/live/your-domain.com/fullchain.pem ./data/nginx-ssl/cert.pem`
|
||||
4. Copy key: `cp /etc/letsencrypt/live/your-domain.com/privkey.pem ./data/nginx-ssl/key.pem`
|
||||
5. Fix permissions: `sudo chown 101:101 ./data/nginx-ssl/*`
|
||||
6. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||
|
||||
### Option B: Commercial Certificate
|
||||
1. Place your certificate files in `./data/nginx-ssl/cert.pem` and `./data/nginx-ssl/key.pem`
|
||||
2. Fix permissions: `sudo chown 101:101 ./data/nginx-ssl/*`
|
||||
3. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Port 80/443 in use | `sudo netstat -tlnp \| grep :80` or `:443` |
|
||||
| Certificate permission denied | `sudo chown 101:101 ./data/nginx-ssl/*` |
|
||||
| Nginx won't start | `docker logs digiserver-nginx` |
|
||||
| Connection refused | Check firewall: `sudo ufw allow 80/tcp && sudo ufw allow 443/tcp` |
|
||||
|
||||
## File Locations
|
||||
- Main config: `./nginx.conf`
|
||||
- SSL certs: `./data/nginx-ssl/`
|
||||
- Logs: `./data/nginx-logs/`
|
||||
- Custom domains: `./nginx-custom-domains.conf` (auto-generated)
|
||||
|
||||
## Next: Production Setup
|
||||
1. Update `.env` with your DOMAIN and EMAIL
|
||||
2. Configure HTTPS settings in admin panel
|
||||
3. Run: `python nginx_manager.py generate`
|
||||
4. Test: `docker exec digiserver-nginx nginx -t`
|
||||
5. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||
56
PROXY_FIX_SETUP.md
Normal file
56
PROXY_FIX_SETUP.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# ProxyFix Middleware Setup - DigiServer v2
|
||||
|
||||
## Overview
|
||||
ProxyFix middleware is now properly configured in the Flask app to handle reverse proxy headers from Nginx (or Caddy). This ensures correct handling of:
|
||||
- **X-Real-IP**: Client's real IP address
|
||||
- **X-Forwarded-For**: List of IPs in the proxy chain
|
||||
- **X-Forwarded-Proto**: Original protocol (http/https)
|
||||
- **X-Forwarded-Host**: Original hostname
|
||||
|
||||
## Configuration Details
|
||||
|
||||
### Flask App (app/app.py)
|
||||
```python
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `x_for=1`: Trust one proxy for X-Forwarded-For header
|
||||
- `x_proto=1`: Trust proxy for X-Forwarded-Proto header
|
||||
- `x_host=1`: Trust proxy for X-Forwarded-Host header
|
||||
- `x_port=1`: Trust proxy for X-Forwarded-Port header
|
||||
|
||||
### Config Settings (app/config.py)
|
||||
|
||||
```python
|
||||
# Reverse proxy trust (for Nginx/Caddy with ProxyFix middleware)
|
||||
TRUSTED_PROXIES = os.getenv('TRUSTED_PROXIES', '127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16')
|
||||
PREFERRED_URL_SCHEME = os.getenv('PREFERRED_URL_SCHEME', 'https')
|
||||
```
|
||||
|
||||
## Testing ProxyFix
|
||||
|
||||
### 1. Test Real Client IP
|
||||
```bash
|
||||
docker exec digiserver-app flask shell
|
||||
>>> from flask import request
|
||||
>>> request.remote_addr # Should show client IP
|
||||
```
|
||||
|
||||
### 2. Test URL Scheme
|
||||
```bash
|
||||
docker exec digiserver-app flask shell
|
||||
>>> from flask import url_for
|
||||
>>> url_for('auth.login', _external=True) # Should use https://
|
||||
```
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [x] ProxyFix imported in app.py
|
||||
- [x] app.wsgi_app wrapped with ProxyFix
|
||||
- [x] TRUSTED_PROXIES configured
|
||||
- [x] PREFERRED_URL_SCHEME set to 'https'
|
||||
- [x] SESSION_COOKIE_SECURE=True in ProductionConfig
|
||||
- [x] Nginx headers configured correctly
|
||||
@@ -4,6 +4,7 @@ Modern Flask application with blueprint architecture
|
||||
"""
|
||||
import os
|
||||
from flask import Flask, render_template
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
|
||||
@@ -37,6 +38,10 @@ def create_app(config_name=None):
|
||||
|
||||
app.config.from_object(config)
|
||||
|
||||
# Apply ProxyFix middleware for reverse proxy (Nginx/Caddy)
|
||||
# This ensures proper handling of X-Forwarded-* headers
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
|
||||
|
||||
# Initialize extensions
|
||||
db.init_app(app)
|
||||
bcrypt.init_app(app)
|
||||
|
||||
@@ -11,6 +11,7 @@ from app.extensions import db, bcrypt
|
||||
from app.models import User, Player, Group, Content, ServerLog, Playlist, HTTPSConfig
|
||||
from app.utils.logger import log_action
|
||||
from app.utils.caddy_manager import CaddyConfigGenerator
|
||||
from app.utils.nginx_config_reader import get_nginx_status
|
||||
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
@@ -870,10 +871,14 @@ def https_config():
|
||||
db.session.commit()
|
||||
log_action('info', f'HTTPS status auto-corrected to enabled (detected from request)')
|
||||
|
||||
# Get Nginx configuration status
|
||||
nginx_status = get_nginx_status()
|
||||
|
||||
return render_template('admin/https_config.html',
|
||||
config=config,
|
||||
is_https_active=is_https_active,
|
||||
current_host=current_host)
|
||||
current_host=current_host,
|
||||
nginx_status=nginx_status)
|
||||
except Exception as e:
|
||||
log_action('error', f'Error loading HTTPS config page: {str(e)}')
|
||||
flash('Error loading HTTPS configuration page.', 'danger')
|
||||
|
||||
@@ -29,6 +29,11 @@ class Config:
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
# Reverse proxy trust (for Nginx/Caddy with ProxyFix middleware)
|
||||
# These are set by werkzeug.middleware.proxy_fix
|
||||
TRUSTED_PROXIES = os.getenv('TRUSTED_PROXIES', '127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16')
|
||||
PREFERRED_URL_SCHEME = os.getenv('PREFERRED_URL_SCHEME', 'https')
|
||||
|
||||
# Cache
|
||||
SEND_FILE_MAX_AGE_DEFAULT = 300 # 5 minutes for static files
|
||||
|
||||
|
||||
@@ -160,6 +160,95 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Nginx Status Card -->
|
||||
<div class="card nginx-status-card">
|
||||
<h2>🔧 Nginx Reverse Proxy Status</h2>
|
||||
{% if nginx_status.available %}
|
||||
<div class="nginx-status-content">
|
||||
<div class="status-item">
|
||||
<strong>Status:</strong>
|
||||
<span class="badge badge-success">✅ Nginx Configured</span>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<strong>Configuration Path:</strong>
|
||||
<code>{{ nginx_status.path }}</code>
|
||||
</div>
|
||||
|
||||
{% if nginx_status.ssl_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-success">🔒 Enabled</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-warning">⚠️ Not Configured</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.http_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTP Ports:</strong>
|
||||
<code>{{ nginx_status.http_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.https_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTPS Ports:</strong>
|
||||
<code>{{ nginx_status.https_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.server_names %}
|
||||
<div class="status-item">
|
||||
<strong>Server Names:</strong>
|
||||
{% for name in nginx_status.server_names %}
|
||||
<code>{{ name }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.upstream_servers %}
|
||||
<div class="status-item">
|
||||
<strong>Upstream Servers:</strong>
|
||||
{% for server in nginx_status.upstream_servers %}
|
||||
<code>{{ server }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.ssl_protocols %}
|
||||
<div class="status-item">
|
||||
<strong>SSL Protocols:</strong>
|
||||
<code>{{ nginx_status.ssl_protocols|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.client_max_body_size %}
|
||||
<div class="status-item">
|
||||
<strong>Max Body Size:</strong>
|
||||
<code>{{ nginx_status.client_max_body_size }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.gzip_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>Gzip Compression:</strong>
|
||||
<span class="badge badge-success">✅ Enabled</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="status-disabled">
|
||||
<p>⚠️ <strong>Nginx configuration not accessible</strong></p>
|
||||
<p>Error: {{ nginx_status.error|default('Unknown error') }}</p>
|
||||
<p style="font-size: 12px; color: #666;">Path checked: {{ nginx_status.path }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Information Section -->
|
||||
<div class="card info-card">
|
||||
<h2>ℹ️ Important Information</h2>
|
||||
@@ -466,6 +555,45 @@
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.nginx-status-card {
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #e7f3ff 100%);
|
||||
border-left: 5px solid #0066cc;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.nginx-status-card h2 {
|
||||
color: #0066cc;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nginx-status-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
border-left: 3px solid #0066cc;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item strong {
|
||||
display: inline-block;
|
||||
min-width: 150px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-item code {
|
||||
background: #f0f7ff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #0066cc;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
|
||||
120
app/utils/nginx_config_reader.py
Normal file
120
app/utils/nginx_config_reader.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Nginx configuration reader utility."""
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
|
||||
class NginxConfigReader:
|
||||
"""Read and parse Nginx configuration files."""
|
||||
|
||||
def __init__(self, config_path: str = '/etc/nginx/nginx.conf'):
|
||||
"""Initialize Nginx config reader."""
|
||||
self.config_path = config_path
|
||||
self.config_content = None
|
||||
self.is_available = os.path.exists(config_path)
|
||||
|
||||
if self.is_available:
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
self.config_content = f.read()
|
||||
except Exception as e:
|
||||
self.is_available = False
|
||||
self.error = str(e)
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""Get Nginx configuration status."""
|
||||
if not self.is_available:
|
||||
return {
|
||||
'available': False,
|
||||
'error': 'Nginx configuration not found',
|
||||
'path': self.config_path
|
||||
}
|
||||
|
||||
return {
|
||||
'available': True,
|
||||
'path': self.config_path,
|
||||
'file_exists': os.path.exists(self.config_path),
|
||||
'ssl_enabled': self._check_ssl_enabled(),
|
||||
'http_ports': self._extract_http_ports(),
|
||||
'https_ports': self._extract_https_ports(),
|
||||
'upstream_servers': self._extract_upstream_servers(),
|
||||
'server_names': self._extract_server_names(),
|
||||
'ssl_protocols': self._extract_ssl_protocols(),
|
||||
'client_max_body_size': self._extract_client_max_body_size(),
|
||||
'gzip_enabled': self._check_gzip_enabled(),
|
||||
}
|
||||
|
||||
def _check_ssl_enabled(self) -> bool:
|
||||
"""Check if SSL is enabled."""
|
||||
if not self.config_content:
|
||||
return False
|
||||
return 'ssl_certificate' in self.config_content
|
||||
|
||||
def _extract_http_ports(self) -> List[int]:
|
||||
"""Extract HTTP listening ports."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'listen\s+(\d+)'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
return sorted(list(set(int(p) for p in matches if int(p) < 1000)))
|
||||
|
||||
def _extract_https_ports(self) -> List[int]:
|
||||
"""Extract HTTPS listening ports."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'listen\s+(\d+).*ssl'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
return sorted(list(set(int(p) for p in matches)))
|
||||
|
||||
def _extract_upstream_servers(self) -> List[str]:
|
||||
"""Extract upstream servers."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
upstream_match = re.search(r'upstream\s+\w+\s*{([^}]+)}', self.config_content)
|
||||
if upstream_match:
|
||||
upstream_content = upstream_match.group(1)
|
||||
servers = re.findall(r'server\s+([^\s;]+)', upstream_content)
|
||||
return servers
|
||||
return []
|
||||
|
||||
def _extract_server_names(self) -> List[str]:
|
||||
"""Extract server names."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'server_name\s+([^;]+);'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
result = []
|
||||
for match in matches:
|
||||
names = match.strip().split()
|
||||
result.extend(names)
|
||||
return result
|
||||
|
||||
def _extract_ssl_protocols(self) -> List[str]:
|
||||
"""Extract SSL protocols."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'ssl_protocols\s+([^;]+);'
|
||||
match = re.search(pattern, self.config_content)
|
||||
if match:
|
||||
return match.group(1).strip().split()
|
||||
return []
|
||||
|
||||
def _extract_client_max_body_size(self) -> Optional[str]:
|
||||
"""Extract client max body size."""
|
||||
if not self.config_content:
|
||||
return None
|
||||
pattern = r'client_max_body_size\s+([^;]+);'
|
||||
match = re.search(pattern, self.config_content)
|
||||
return match.group(1).strip() if match else None
|
||||
|
||||
def _check_gzip_enabled(self) -> bool:
|
||||
"""Check if gzip is enabled."""
|
||||
if not self.config_content:
|
||||
return False
|
||||
return bool(re.search(r'gzip\s+on\s*;', self.config_content))
|
||||
|
||||
|
||||
def get_nginx_status() -> Dict[str, Any]:
|
||||
"""Get Nginx configuration status."""
|
||||
reader = NginxConfigReader()
|
||||
return reader.get_status()
|
||||
@@ -26,19 +26,19 @@ services:
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
# Caddy reverse proxy with automatic HTTPS
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: digiserver-caddy
|
||||
# Nginx reverse proxy with HTTPS support
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: digiserver-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3 support
|
||||
- "2019:2019" # Caddy admin API
|
||||
volumes:
|
||||
- ./data/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./data/caddy-data:/data
|
||||
- ./data/caddy-config:/config
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf:rw
|
||||
- ./data/nginx-ssl:/etc/nginx/ssl:ro
|
||||
- ./data/nginx-logs:/var/log/nginx
|
||||
- ./data/certbot:/var/www/certbot:ro # For Let's Encrypt ACME challenges
|
||||
environment:
|
||||
- DOMAIN=${DOMAIN:-localhost}
|
||||
- EMAIL=${EMAIL:-admin@localhost}
|
||||
@@ -46,6 +46,12 @@ services:
|
||||
digiserver-app:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
|
||||
30
generate_nginx_certs.sh
Executable file
30
generate_nginx_certs.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Generate self-signed SSL certificates for Nginx
|
||||
# Usage: ./generate_nginx_certs.sh [domain] [days]
|
||||
|
||||
DOMAIN=${1:-localhost}
|
||||
DAYS=${2:-365}
|
||||
CERT_DIR="./data/nginx-ssl"
|
||||
|
||||
echo "🔐 Generating self-signed SSL certificate for Nginx"
|
||||
echo "Domain: $DOMAIN"
|
||||
echo "Valid for: $DAYS days"
|
||||
echo "Certificate directory: $CERT_DIR"
|
||||
|
||||
# Create directory if it doesnt exist
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
# Generate private key and certificate
|
||||
openssl req -x509 -nodes -days "$DAYS" \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "$CERT_DIR/key.pem" \
|
||||
-out "$CERT_DIR/cert.pem" \
|
||||
-subj "/CN=$DOMAIN/O=DigiServer/C=US"
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 "$CERT_DIR/cert.pem"
|
||||
chmod 600 "$CERT_DIR/key.pem"
|
||||
|
||||
echo "✅ Certificates generated successfully!"
|
||||
echo "Certificate: $CERT_DIR/cert.pem"
|
||||
echo "Key: $CERT_DIR/key.pem"
|
||||
21
nginx-custom-domains.conf
Normal file
21
nginx-custom-domains.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
# Nginx configuration for custom HTTPS domains
|
||||
# This file will be dynamically generated based on HTTPSConfig database entries
|
||||
# Include this in your nginx.conf with: include /etc/nginx/conf.d/custom-domains.conf;
|
||||
|
||||
# Example entry for custom domain:
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# listen [::]:443 ssl http2;
|
||||
# server_name digiserver.example.com;
|
||||
#
|
||||
# ssl_certificate /etc/nginx/ssl/custom/cert.pem;
|
||||
# ssl_certificate_key /etc/nginx/ssl/custom/key.pem;
|
||||
#
|
||||
# location / {
|
||||
# proxy_pass http://digiserver_app;
|
||||
# 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;
|
||||
# }
|
||||
# }
|
||||
117
nginx.conf
Normal file
117
nginx.conf
Normal file
@@ -0,0 +1,117 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 2048M;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml;
|
||||
|
||||
# Upstream to Flask application
|
||||
upstream digiserver_app {
|
||||
server digiserver-app:5000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# HTTP Server - redirect to HTTPS
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
# Allow ACME challenges for Let's Encrypt
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect HTTP to HTTPS for non-ACME requests
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Server (with self-signed cert by default)
|
||||
server {
|
||||
listen 443 ssl http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
server_name localhost;
|
||||
|
||||
# SSL certificate paths (will be volume-mounted)
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
# SSL Configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# 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 "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# Proxy settings
|
||||
location / {
|
||||
proxy_pass http://digiserver_app;
|
||||
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_set_header X-Forwarded-Host $server_name;
|
||||
|
||||
# Timeouts for large uploads
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
proxy_busy_buffers_size 256k;
|
||||
}
|
||||
|
||||
# Static files caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
proxy_pass http://digiserver_app;
|
||||
proxy_cache_valid 200 60d;
|
||||
expires 60d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# Additional server blocks for custom domains can be included here
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
Reference in New Issue
Block a user