Add SSL/TLS support with Certbot and update deployment guide
- nginx/ssl.conf: full HTTPS config with HTTP→HTTPS redirect, modern TLS settings, HSTS header, and ACME challenge passthrough for renewals - nginx/certbot-init.conf: minimal HTTP config for initial cert provisioning - docker-compose.ssl.yml: compose override adding port 443, certbot volumes, and auto-renewal sidecar container - docs/DEPLOYMENT.md: comprehensive 3-phase SSL walkthrough (obtain cert, enable SSL, auto-renewal) with day-to-day usage and revert instructions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
28
docker-compose.ssl.yml
Normal file
28
docker-compose.ssl.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# SSL override — use with: docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d
|
||||||
|
#
|
||||||
|
# This adds port 443, certbot volumes, and a certbot renewal service
|
||||||
|
# to the base docker-compose.yml configuration.
|
||||||
|
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/ssl.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- certbot_www:/var/www/certbot:ro
|
||||||
|
- certbot_conf:/etc/letsencrypt:ro
|
||||||
|
|
||||||
|
certbot:
|
||||||
|
image: certbot/certbot:latest
|
||||||
|
volumes:
|
||||||
|
- certbot_www:/var/www/certbot
|
||||||
|
- certbot_conf:/etc/letsencrypt
|
||||||
|
networks:
|
||||||
|
- hoanet
|
||||||
|
# Auto-renew: check twice daily, only renews if < 30 days remain
|
||||||
|
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done'"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
certbot_www:
|
||||||
|
certbot_conf:
|
||||||
@@ -9,11 +9,12 @@
|
|||||||
|
|
||||||
1. [Prerequisites](#prerequisites)
|
1. [Prerequisites](#prerequisites)
|
||||||
2. [Deploy to a Fresh Docker Server](#deploy-to-a-fresh-docker-server)
|
2. [Deploy to a Fresh Docker Server](#deploy-to-a-fresh-docker-server)
|
||||||
3. [Backup the Local Test Database](#backup-the-local-test-database)
|
3. [SSL with Certbot (Let's Encrypt)](#ssl-with-certbot-lets-encrypt)
|
||||||
4. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment)
|
4. [Backup the Local Test Database](#backup-the-local-test-database)
|
||||||
5. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment)
|
5. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment)
|
||||||
6. [Verifying the Deployment](#verifying-the-deployment)
|
6. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment)
|
||||||
7. [Environment Variable Reference](#environment-variable-reference)
|
7. [Verifying the Deployment](#verifying-the-deployment)
|
||||||
|
8. [Environment Variable Reference](#environment-variable-reference)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,6 +32,11 @@ On the **target server**, ensure the following are installed:
|
|||||||
The app runs five containers — nginx, backend (NestJS), frontend (Vite/React),
|
The app runs five containers — nginx, backend (NestJS), frontend (Vite/React),
|
||||||
PostgreSQL 15, and Redis 7. Total memory footprint is roughly **1–2 GB** idle.
|
PostgreSQL 15, and Redis 7. Total memory footprint is roughly **1–2 GB** idle.
|
||||||
|
|
||||||
|
For SSL, the server must also have:
|
||||||
|
- A **public hostname** with a DNS A record pointing to the server's IP
|
||||||
|
(e.g., `staging.yourdomain.com → 203.0.113.10`)
|
||||||
|
- **Ports 80 and 443** open in any firewall / security group
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Deploy to a Fresh Docker Server
|
## Deploy to a Fresh Docker Server
|
||||||
@@ -129,8 +135,240 @@ This creates:
|
|||||||
| API | `http://<server-ip>/api` |
|
| API | `http://<server-ip>/api` |
|
||||||
| Postgres | `<server-ip>:5432` (direct) |
|
| Postgres | `<server-ip>:5432` (direct) |
|
||||||
|
|
||||||
> **Note:** For production, add an SSL-terminating proxy (Caddy, Traefik, or
|
> At this point the app is running over **plain HTTP**. Continue to the next
|
||||||
> an nginx TLS config) in front of port 80.
|
> section to enable HTTPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSL with Certbot (Let's Encrypt)
|
||||||
|
|
||||||
|
This section walks through enabling HTTPS using the included nginx container
|
||||||
|
and a Certbot sidecar. The process has three phases: obtain the certificate,
|
||||||
|
switch nginx to SSL, and set up auto-renewal.
|
||||||
|
|
||||||
|
### Files involved
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `nginx/default.conf` | HTTP-only config (development / initial state) |
|
||||||
|
| `nginx/certbot-init.conf` | Temporary config used only during initial cert request |
|
||||||
|
| `nginx/ssl.conf` | Full SSL config (HTTP→HTTPS redirect + TLS termination) |
|
||||||
|
| `docker-compose.ssl.yml` | Compose override that adds port 443, certbot volumes, and renewal service |
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 — Obtain certificate
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ nginx serves certbot-init.conf on port 80 │
|
||||||
|
│ certbot answers ACME challenge → gets cert │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Phase 2 — Enable SSL
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ nginx switches to ssl.conf │
|
||||||
|
│ port 80 → 301 redirect to 443 │
|
||||||
|
│ port 443 → TLS termination → backend/frontend │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Phase 3 — Auto-renewal
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ certbot container checks every 12 hours │
|
||||||
|
│ renews if cert expires within 30 days │
|
||||||
|
│ nginx reloads via cron to pick up new cert │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1 — Obtain the certificate
|
||||||
|
|
||||||
|
Throughout this section, replace `staging.example.com` with your actual
|
||||||
|
hostname and `you@example.com` with your real email.
|
||||||
|
|
||||||
|
#### Step 1: Edit `nginx/ssl.conf` with your hostname
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/hoa-ledgeriq
|
||||||
|
|
||||||
|
sed -i 's/staging.example.com/YOUR_HOSTNAME/g' nginx/ssl.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
This updates the three places where the hostname appears: the `server_name`
|
||||||
|
directive and the two certificate paths.
|
||||||
|
|
||||||
|
#### Step 2: Swap in the temporary certbot-init config
|
||||||
|
|
||||||
|
Nginx can't start with `ssl.conf` yet because the certificates don't exist.
|
||||||
|
Use the minimal HTTP-only config that only serves the ACME challenge:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Back up the current config
|
||||||
|
cp nginx/default.conf nginx/default.conf.bak
|
||||||
|
|
||||||
|
# Use the certbot init config temporarily
|
||||||
|
cp nginx/certbot-init.conf nginx/default.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Start the stack with the SSL compose override
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This starts all services plus the certbot container, exposes port 443,
|
||||||
|
and creates the shared volumes for certificates and ACME challenges.
|
||||||
|
|
||||||
|
#### Step 4: Request the certificate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml \
|
||||||
|
run --rm certbot certonly \
|
||||||
|
--webroot \
|
||||||
|
--webroot-path /var/www/certbot \
|
||||||
|
--email you@example.com \
|
||||||
|
--agree-tos \
|
||||||
|
--no-eff-email \
|
||||||
|
-d YOUR_HOSTNAME
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see output ending with:
|
||||||
|
|
||||||
|
```
|
||||||
|
Successfully received certificate.
|
||||||
|
Certificate is saved at: /etc/letsencrypt/live/YOUR_HOSTNAME/fullchain.pem
|
||||||
|
Key is saved at: /etc/letsencrypt/live/YOUR_HOSTNAME/privkey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Troubleshooting:** If certbot fails with a connection error, ensure:
|
||||||
|
> - DNS for `YOUR_HOSTNAME` resolves to this server's public IP
|
||||||
|
> - Port 80 is open (firewall, security group, cloud provider)
|
||||||
|
> - No other process is bound to port 80
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2 — Switch to full SSL
|
||||||
|
|
||||||
|
#### Step 5: Activate the SSL nginx config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp nginx/ssl.conf nginx/default.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 6: Restart nginx to load the certificate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml \
|
||||||
|
exec nginx nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 7: Verify HTTPS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should follow redirect and return HTML
|
||||||
|
curl -I https://YOUR_HOSTNAME
|
||||||
|
|
||||||
|
# Should return 301 redirect
|
||||||
|
curl -I http://YOUR_HOSTNAME
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected `http://` response:
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP/1.1 301 Moved Permanently
|
||||||
|
Location: https://YOUR_HOSTNAME/
|
||||||
|
```
|
||||||
|
|
||||||
|
The app is now accessible at `https://YOUR_HOSTNAME` with a valid
|
||||||
|
Let's Encrypt certificate. All HTTP requests redirect to HTTPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3 — Auto-renewal
|
||||||
|
|
||||||
|
The `certbot` service defined in `docker-compose.ssl.yml` already runs a
|
||||||
|
renewal loop (checks every 12 hours, renews if < 30 days remain).
|
||||||
|
|
||||||
|
However, nginx needs to be told to reload when a new certificate is issued.
|
||||||
|
Add a host-level cron job that reloads nginx daily:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to the server's crontab (as root)
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
Add this line:
|
||||||
|
|
||||||
|
```cron
|
||||||
|
0 4 * * * cd /opt/hoa-ledgeriq && docker compose -f docker-compose.yml -f docker-compose.ssl.yml exec -T nginx nginx -s reload > /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
This reloads nginx at 4 AM daily. Since reload is graceful (no downtime),
|
||||||
|
this is safe to run even when no new cert was issued.
|
||||||
|
|
||||||
|
> **Alternative:** If you prefer not to use cron, you can add a
|
||||||
|
> `--deploy-hook` to the certbot command that reloads nginx only when a
|
||||||
|
> renewal actually happens:
|
||||||
|
> ```bash
|
||||||
|
> docker compose -f docker-compose.yml -f docker-compose.ssl.yml \
|
||||||
|
> run --rm certbot certonly \
|
||||||
|
> --webroot \
|
||||||
|
> --webroot-path /var/www/certbot \
|
||||||
|
> --email you@example.com \
|
||||||
|
> --agree-tos \
|
||||||
|
> --no-eff-email \
|
||||||
|
> --deploy-hook "nginx -s reload" \
|
||||||
|
> -d YOUR_HOSTNAME
|
||||||
|
> ```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Day-to-day usage with SSL
|
||||||
|
|
||||||
|
Once SSL is set up, **always** use both compose files when running commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start / restart
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d
|
||||||
|
|
||||||
|
# Rebuild after code changes
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d --build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.ssl.yml logs -f nginx
|
||||||
|
|
||||||
|
# Shortcut: create an alias
|
||||||
|
echo 'alias dc="docker compose -f docker-compose.yml -f docker-compose.ssl.yml"' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
dc up -d --build # much easier
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reverting to HTTP-only (development)
|
||||||
|
|
||||||
|
If you need to go back to plain HTTP (e.g., local development):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore the original HTTP-only config
|
||||||
|
cp nginx/default.conf.bak nginx/default.conf
|
||||||
|
|
||||||
|
# Run without the SSL override
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Quick reference: SSL file map
|
||||||
|
|
||||||
|
```
|
||||||
|
nginx/
|
||||||
|
├── default.conf ← active config (copy ssl.conf here for HTTPS)
|
||||||
|
├── default.conf.bak ← backup of original HTTP-only config
|
||||||
|
├── certbot-init.conf ← temporary, used only during initial cert request
|
||||||
|
└── ssl.conf ← full SSL config (edit hostname before using)
|
||||||
|
|
||||||
|
docker-compose.yml ← base stack (HTTP only, ports 80)
|
||||||
|
docker-compose.ssl.yml ← SSL overlay (adds 443, certbot, volumes)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -301,7 +539,7 @@ docker compose exec -T postgres psql \
|
|||||||
### Quick health checks
|
### Quick health checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Backend is responding
|
# Backend is responding (use https:// if SSL is enabled)
|
||||||
curl -s http://localhost/api/auth/login | head -c 100
|
curl -s http://localhost/api/auth/login | head -c 100
|
||||||
|
|
||||||
# Database is accessible
|
# Database is accessible
|
||||||
@@ -314,19 +552,31 @@ docker compose exec -T redis redis-cli ping
|
|||||||
|
|
||||||
### Full smoke test
|
### Full smoke test
|
||||||
|
|
||||||
1. Open `http://<server-ip>` in a browser
|
1. Open `https://YOUR_HOSTNAME` (or `http://<server-ip>`) in a browser
|
||||||
2. Log in with a known account
|
2. Log in with a known account
|
||||||
3. Navigate to Dashboard — verify health scores load
|
3. Navigate to Dashboard — verify health scores load
|
||||||
4. Navigate to Capital Planning — verify Kanban columns render
|
4. Navigate to Capital Planning — verify Kanban columns render
|
||||||
5. Navigate to Projects — verify project list loads
|
5. Navigate to Projects — verify project list loads
|
||||||
6. Check the Settings page — version should read **2026.3.2 (beta)**
|
6. Check the Settings page — version should read **2026.3.2 (beta)**
|
||||||
|
|
||||||
|
### Verify SSL (if enabled)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check certificate details
|
||||||
|
echo | openssl s_client -connect YOUR_HOSTNAME:443 -servername YOUR_HOSTNAME 2>/dev/null \
|
||||||
|
| openssl x509 -noout -subject -issuer -dates
|
||||||
|
|
||||||
|
# Check that HTTP redirects to HTTPS
|
||||||
|
curl -sI http://YOUR_HOSTNAME | grep -E 'HTTP|Location'
|
||||||
|
```
|
||||||
|
|
||||||
### View logs
|
### View logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose logs -f # all services
|
docker compose logs -f # all services
|
||||||
docker compose logs -f backend # backend only
|
docker compose logs -f backend # backend only
|
||||||
docker compose logs -f postgres # database only
|
docker compose logs -f postgres # database only
|
||||||
|
docker compose logs -f nginx # nginx access/error log
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -352,21 +602,21 @@ docker compose logs -f postgres # database only
|
|||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────┐
|
┌──────────────────┐
|
||||||
Browser ────────► │ nginx :80 │
|
Browser ─────────► │ nginx :80/:443 │
|
||||||
└──────┬──────┘
|
└────────┬─────────┘
|
||||||
┌────────┴────────┐
|
┌──────────┴──────────┐
|
||||||
▼ ▼
|
▼ ▼
|
||||||
┌──────────────┐ ┌──────────────┐
|
┌──────────────┐ ┌──────────────┐
|
||||||
│ backend :3000│ │frontend :5173│
|
│ backend :3000│ │frontend :5173│
|
||||||
│ (NestJS) │ │ (Vite/React) │
|
│ (NestJS) │ │ (Vite/React) │
|
||||||
└──────┬───────┘ └──────────────┘
|
└──────┬───────┘ └──────────────┘
|
||||||
┌────┴────┐
|
┌────┴────┐
|
||||||
▼ ▼
|
▼ ▼
|
||||||
┌────────────┐ ┌───────────┐
|
┌────────────┐ ┌───────────┐ ┌───────────┐
|
||||||
│postgres:5432│ │redis :6379│
|
│postgres:5432│ │redis :6379│ │ certbot │
|
||||||
│ (PG 15) │ │ (Redis 7) │
|
│ (PG 15) │ │ (Redis 7) │ │ (renewal) │
|
||||||
└────────────┘ └───────────┘
|
└────────────┘ └───────────┘ └───────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**Multi-tenant isolation:** Each HOA organization gets its own PostgreSQL
|
**Multi-tenant isolation:** Each HOA organization gets its own PostgreSQL
|
||||||
|
|||||||
18
nginx/certbot-init.conf
Normal file
18
nginx/certbot-init.conf
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Temporary nginx config — used ONLY during the initial certbot certificate
|
||||||
|
# request. Once the cert is obtained, switch to ssl.conf and restart nginx.
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Certbot ACME challenge
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return 503 for everything else so it's obvious this is not the real app
|
||||||
|
location / {
|
||||||
|
return 503 "SSL certificate is being provisioned. Try again in a minute.\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
nginx/ssl.conf
Normal file
106
nginx/ssl.conf
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
upstream backend {
|
||||||
|
server backend:3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream frontend {
|
||||||
|
server frontend:5173;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect all HTTP to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Let certbot answer ACME challenges over HTTP
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything else -> HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS server
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
# Replace with your actual hostname:
|
||||||
|
server_name staging.example.com;
|
||||||
|
|
||||||
|
# --- TLS certificates (managed by certbot) ---
|
||||||
|
ssl_certificate /etc/letsencrypt/live/staging.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/staging.example.com/privkey.pem;
|
||||||
|
|
||||||
|
# --- Modern TLS settings ---
|
||||||
|
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 on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
|
# --- Security headers ---
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||||
|
add_header X-Content-Type-Options nosniff always;
|
||||||
|
add_header X-Frame-Options SAMEORIGIN always;
|
||||||
|
|
||||||
|
# --- Proxy routes (same as default.conf) ---
|
||||||
|
|
||||||
|
# API requests -> NestJS backend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# AI recommendation endpoint needs a longer timeout (up to 3 minutes)
|
||||||
|
location /api/investment-planning/recommendations {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
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;
|
||||||
|
proxy_read_timeout 180s;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# AI health-score endpoint also needs a longer timeout
|
||||||
|
location /api/health-scores/calculate {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
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;
|
||||||
|
proxy_read_timeout 180s;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything else -> Vite dev server (frontend)
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frontend;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user