nordabiz/docs/architecture/03-deployment-architecture.md
Maciej Pienczyn bfd48a6e20 docs: aktualizacja architektury po migracji na OVH VPS + e-deklaracja w roadmap
- ROADMAP: dodano funkcję #2 (e-deklaracja PZ) z analizą flow PDF + samodzielny podpis
- architecture/03,07,08,09,11 + flows/06: aktualizacja pod OVH VPS (IP, user maciejpi zamiast www-data, brak NPM dla prod)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:00:14 +02:00

35 KiB

Deployment Architecture Diagram

Document Version: 1.0 Last Updated: 2026-04-04 Status: Production LIVE Diagram Type: Infrastructure / Deployment View


NOTE (2026-04-04): Production migrated to OVH VPS (57.128.200.27, hostname inpi-vps-waw01). Traffic flow: Internet -> Nginx (57.128.200.27:443) -> Gunicorn (127.0.0.1:5000). No FortiGate or NPM for production. NPM (10.22.68.250) serves staging only.

Overview

This diagram shows the physical deployment architecture of the Norda Biznes Partner system. It illustrates:

  • Physical servers and their infrastructure details (VMs, IPs, ports)
  • Network topology and connectivity
  • Service distribution across servers
  • Critical port mappings and firewall rules
  • External access points and NAT configuration

Abstraction Level: Infrastructure / Deployment Audience: DevOps, System Administrators, Infrastructure Team Purpose: Understanding physical deployment, troubleshooting connectivity issues, incident response


Deployment Architecture Diagram

graph TB
    %% External actors and entry points
    Internet["🌐 INTERNET<br/>External Users"]

    %% Production server (OVH VPS)
    subgraph "OVH VPS [inpi-vps-waw01 | 57.128.200.27]"
        subgraph "Reverse Proxy"
            Nginx["🔒 NGINX<br/>Ports: 443 (HTTPS), 80 (redirect)<br/><br/>SSL: Let's Encrypt (certbot)<br/>Domains: nordabiznes.pl"]
        end

        subgraph "Application Layer"
            Gunicorn["🌐 GUNICORN + FLASK<br/>Port: 127.0.0.1:5000<br/>Workers: 4<br/>User: maciejpi<br/><br/>App: /var/www/nordabiznes<br/>Service: nordabiznes.service<br/>Timeout: 120s"]
        end

        subgraph "Data Layer"
            PostgreSQL["💾 POSTGRESQL 14<br/>Port: 5432<br/>Listen: 127.0.0.1 ONLY<br/><br/>Database: nordabiz<br/>User: nordabiz_app<br/>Tables: 36<br/><br/>No external connections!"]
        end

        subgraph "Background Jobs"
            Scripts["⚙️ PYTHON SCRIPTS<br/>Execution: Cron / Manual<br/><br/>• seo_audit.py<br/>• social_media_audit.py<br/>• gbp_audit.py<br/>• fetch_company_news.py"]
        end
    end

    %% External services
    subgraph "External APIs (HTTPS)"
        Gemini["🤖 Google Gemini AI<br/>generativelanguage.googleapis.com<br/>Auth: API Key"]
        BraveAPI["🔍 Brave Search API<br/>api.search.brave.com<br/>Auth: API Key"]
        PageSpeed["📊 Google PageSpeed API<br/>googleapis.com/pagespeedonline<br/>Auth: API Key"]
        Places["📍 Google Places API<br/>maps.googleapis.com/maps/api<br/>Auth: API Key"]
        KRS["🏛️ KRS Open API<br/>api-krs.ms.gov.pl<br/>Auth: Public"]
        MSGraph["📧 Microsoft Graph API<br/>graph.microsoft.com<br/>Auth: OAuth 2.0"]
    end

    %% User traffic flow
    Internet -->|"HTTPS :443<br/>HTTP :80"| Nginx

    %% Nginx to backend
    Nginx -->|"HTTP<br/>127.0.0.1:5000"| Gunicorn

    %% Application to database (localhost only)
    Gunicorn <-->|"SQL<br/>localhost:5432<br/>SQLAlchemy ORM"| PostgreSQL
    Scripts <-->|"SQL<br/>127.0.0.1:5432<br/>Direct connection"| PostgreSQL

    %% External API calls
    Gunicorn -->|"HTTPS<br/>API calls"| Gemini
    Gunicorn -->|"HTTPS<br/>API calls"| BraveAPI
    Gunicorn -->|"HTTPS<br/>API calls"| Places
    Gunicorn -->|"HTTPS<br/>API calls"| KRS
    Gunicorn -->|"HTTPS<br/>OAuth 2.0"| MSGraph
    Scripts -->|"HTTPS<br/>Audit requests"| PageSpeed
    Scripts -->|"HTTPS<br/>News search"| BraveAPI

    %% Styling
    classDef appStyle fill:#1168bd,stroke:#0b4884,color:#ffffff,stroke-width:3px
    classDef dbStyle fill:#438dd5,stroke:#2e6295,color:#ffffff,stroke-width:3px
    classDef proxyStyle fill:#e74c3c,stroke:#c0392b,color:#ffffff,stroke-width:4px
    classDef externalStyle fill:#95a5a6,stroke:#7f8c8d,color:#ffffff,stroke-width:2px

    class Nginx proxyStyle
    class Gunicorn,Scripts appStyle
    class PostgreSQL dbStyle
    class Gemini,BraveAPI,PageSpeed,Places,KRS,MSGraph externalStyle

Infrastructure Inventory

Production Server

Server IP Address Hostname OS vCPU RAM Disk Provider
OVH VPS 57.128.200.27 inpi-vps-waw01 Ubuntu 22.04 4 8 GB 80 GB SSD OVH Cloud (Warsaw)

Staging (on-prem, unchanged)

Server VM ID IP Address Hostname OS Hypervisor
NORDABIZ-STAGING-01 248 10.22.68.248 nordabiz-staging-01 Ubuntu 22.04 Proxmox VE

Note: The old on-prem production VM 249 (10.22.68.249) and NPM reverse proxy (10.22.68.250) are no longer used for production. Staging still uses NPM + FortiGate path.


Server Details

OVH VPS (Production Server)

Infrastructure:

  • IP Address: 57.128.200.27
  • Hostname: inpi-vps-waw01
  • OS: Ubuntu 22.04 LTS
  • Resources: 4 vCPU, 8 GB RAM, 80 GB SSD
  • Provider: OVH Cloud (Warsaw datacenter)
  • DNS: nordabiznes.pl -> 57.128.200.27 (A record in OVH DNS)

Services Running:

Service Port Binding User Status Purpose
Nginx 443 0.0.0.0 root Active SSL termination + reverse proxy
Nginx 80 0.0.0.0 root Active HTTP→HTTPS redirect
Gunicorn/Flask 5000 127.0.0.1 maciejpi Active Main Application
PostgreSQL 14 5432 127.0.0.1 postgres Active Database
SSH 22 0.0.0.0 - Active Remote administration

Application Paths:

/var/www/nordabiznes/           # Application root
├── app.py                      # Main Flask application (13,144 lines)
├── database.py                 # SQLAlchemy models (36 tables)
├── venv/                       # Python virtual environment
├── templates/                  # Jinja2 templates
├── static/                     # CSS, JS, images
├── scripts/                    # Background jobs
└── .env                        # Environment variables (secrets)

/var/log/nordabiznes/           # Application logs
├── access.log                  # Gunicorn access log
└── error.log                   # Gunicorn error log

/etc/systemd/system/
└── nordabiznes.service         # Systemd unit file

Service Configuration:

# Service management
sudo systemctl status nordabiznes
sudo systemctl restart nordabiznes
sudo systemctl start nordabiznes
sudo systemctl stop nordabiznes

# View logs
sudo journalctl -u nordabiznes -f          # Follow logs
sudo journalctl -u nordabiznes -n 100      # Last 100 lines
tail -f /var/log/nordabiznes/access.log    # Access log
tail -f /var/log/nordabiznes/error.log     # Error log

SSH Access:

ssh maciejpi@57.128.200.27
# CRITICAL: Always use 'maciejpi' user, NEVER 'root'!

Gunicorn Configuration:

# /etc/systemd/system/nordabiznes.service
[Service]
User=maciejpi
Group=maciejpi
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
    --bind 127.0.0.1:5000 \
    --workers 4 \
    --timeout 120 \
    --access-logfile /var/log/nordabiznes/access.log \
    --error-logfile /var/log/nordabiznes/error.log \
    app:app

Nginx Configuration:

# /etc/nginx/sites-available/nordabiznes.pl
server {
    listen 443 ssl http2;
    server_name nordabiznes.pl www.nordabiznes.pl;
    ssl_certificate /etc/letsencrypt/live/nordabiznes.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nordabiznes.pl/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:5000;
        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;
    }
}

server {
    listen 80;
    server_name nordabiznes.pl www.nordabiznes.pl;
    return 301 https://$server_name$request_uri;
}

SSL Certificate Management:

# Let's Encrypt via certbot
sudo certbot certificates                    # Check certificate status
sudo certbot renew                           # Renew certificates
sudo certbot renew --dry-run                 # Test renewal

Network Topology

Network Segments

Segment CIDR/Address Purpose Security Level
Public Internet 0.0.0.0/0 External user access Untrusted
OVH VPS 57.128.200.27/32 Production server (direct) Production
INPI LAN 10.22.68.0/24 Staging + internal services Trusted
Localhost 127.0.0.1/8 Server-local services Isolated

DNS Configuration

External DNS (OVH):

Type Name Value TTL Purpose
A nordabiznes.pl 57.128.200.27 3600 Main domain (OVH VPS)
A www.nordabiznes.pl 57.128.200.27 3600 WWW subdomain
A staging.nordabiznes.pl 85.237.177.83 3600 Staging (on-prem via FortiGate)

Port Mappings

OVH VPS (57.128.200.27) Port Matrix

Port Protocol Service Binding Access Purpose Security Notes
22 TCP SSH 0.0.0.0 Public (key-only) Server administration Key-based auth
80 TCP Nginx 0.0.0.0 Public HTTP→HTTPS redirect Auto-redirect
443 TCP Nginx 0.0.0.0 Public HTTPS (SSL termination) Let's Encrypt
5000 TCP Gunicorn/Flask 127.0.0.1 Localhost only Main Application Nginx proxy_pass
5432 TCP PostgreSQL 127.0.0.1 Localhost only Database No external access

Traffic flow: Internet -> nginx (:443) -> proxy_pass -> Gunicorn (127.0.0.1:5000)


Port Matrix (historical on-prem, now staging only)

Port Protocol Service Binding Access Purpose Security Notes
22 TCP SSH 0.0.0.0 Internal only Server administration Key-based auth
80 TCP NPM 0.0.0.0 Public (via NAT) HTTP→HTTPS redirect Auto-redirect
81 TCP NPM Admin UI 0.0.0.0 Internal only NPM management Auth required
443 TCP NPM 0.0.0.0 Public (via NAT) HTTPS traffic SSL termination

Note: R11-REVPROXY-01 (10.22.68.250) with NPM is now used for staging only (staging.nordabiznes.pl).


r11-git-inpi (10.22.68.180) Port Matrix (internal, for staging)

Port Protocol Service Binding Access Purpose Security Notes
22 TCP SSH 0.0.0.0 Internal only Server administration Key-based auth
3000 TCP Gitea (HTTPS) 0.0.0.0 Internal only Git repository hosting Self-signed SSL

Fortigate Firewall NAT Rules (staging only)

External Port Protocol Internal IP Internal Port Purpose Traffic
443 TCP 10.22.68.250 443 HTTPS staging access Incoming
80 TCP 10.22.68.250 80 HTTP redirect Incoming

Note: FortiGate NAT rules are now only used for staging (staging.nordabiznes.pl). Production traffic goes directly to OVH VPS (57.128.200.27) without FortiGate.


Network Flow Diagrams

Successful Production Request Flow

┌─────────────────────────────────────────────────────────────────┐
│ 1. USER BROWSER                                                 │
│    https://nordabiznes.pl                                       │
└────────────────────────────┬────────────────────────────────────┘
                             │ DNS: nordabiznes.pl → 57.128.200.27
                             │ HTTPS :443
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. NGINX @ OVH VPS (57.128.200.27:443)                         │
│    • Accept HTTPS connection                                    │
│    • TLS handshake (Let's Encrypt certificate via certbot)     │
│    • Terminate SSL/TLS                                          │
│    • Add security headers (HSTS, etc.)                          │
│    • Proxy pass to Gunicorn                                     │
└────────────────────────────┬────────────────────────────────────┘
                             │ http://127.0.0.1:5000
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. GUNICORN @ OVH VPS (127.0.0.1:5000)                         │
│    • Receive HTTP request (decrypted)                           │
│    • Load balance across 4 workers                              │
│    • Pass to Flask application                                  │
└────────────────────────────┬────────────────────────────────────┘
                             │ Process request
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. FLASK APP (app.py)                                           │
│    • Rate limiting check (Flask-Limiter)                        │
│    • Session validation (Flask-Login)                           │
│    • CSRF protection (Flask-WTF)                                │
│    • Route matching (90+ routes)                                │
│    • Business logic execution                                   │
└────────────────────────────┬────────────────────────────────────┘
                             │ Database query
                             │ localhost:5432
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. POSTGRESQL @ localhost:5432                                  │
│    • Execute SQL query (SQLAlchemy ORM)                         │
│    • Apply constraints and indexes                              │
│    • Return result set                                          │
└────────────────────────────┬────────────────────────────────────┘
                             │ Results
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. FLASK APP (render response)                                  │
│    • Jinja2 template rendering                                  │
│    • JSON serialization (API routes)                            │
│    • Apply response headers                                     │
└────────────────────────────┬────────────────────────────────────┘
                             │ HTTP response
                             ▼
GUNICORN → NGINX (encrypt with TLS) → USER BROWSER

Timing Breakdown:

  • DNS resolution: ~50ms
  • SSL handshake: ~100ms
  • Nginx proxy: ~5ms
  • Flask processing: ~50-500ms (depends on query complexity)
  • Database query: ~10-100ms
  • Template rendering: ~20-50ms
  • Total: ~235-805ms (typical range)

Historical: Failed Request Flow (Port 80 Misconfiguration)

This applied to the old on-prem setup (pre-OVH migration). See docs/INCIDENT_REPORT_20260102.md for details.

The old setup used NPM (10.22.68.250) forwarding to on-prem VM (57.128.200.27). Misconfiguring the forward port to 80 instead of 5000 caused an infinite redirect loop. This is no longer applicable to the current OVH VPS production setup.


SSL/TLS Configuration

Let's Encrypt Certificate

Certificate Details:

  • Provider: Let's Encrypt
  • Managed By: certbot on OVH VPS
  • Domains:
    • nordabiznes.pl
    • www.nordabiznes.pl
  • Key Type: RSA 2048-bit
  • Validity: 90 days (auto-renewed)
  • Renewal: Automatic via certbot cron/timer

TLS Configuration:

  • Protocols: TLS 1.2, TLS 1.3 (TLS 1.0/1.1 disabled)
  • Cipher Suites: Modern ciphers only
  • HTTP/2: Enabled
  • HSTS: Enabled (max-age=31536000, includeSubDomains)

Security Headers (Added by nginx):

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block

Certificate Verification:

# Check certificate details
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl < /dev/null 2>/dev/null | \
  openssl x509 -noout -dates -subject -issuer

# Check certificate status on server
ssh maciejpi@57.128.200.27 "sudo certbot certificates"

# Test SSL configuration
curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 OK

External API Connectivity

Outbound HTTPS Connections

API Endpoint Port Authentication Purpose Rate Limit
Google Gemini AI generativelanguage.googleapis.com 443 API Key AI chat, image analysis Cost-limited
Brave Search api.search.brave.com 443 API Key News monitoring, social discovery 2000/month
Google PageSpeed googleapis.com/pagespeedonline 443 API Key SEO/performance audits 25,000/day
Google Places maps.googleapis.com/maps/api 443 API Key Business profiles, reviews Quota-based
KRS Open API api-krs.ms.gov.pl 443 Public Company verification Unlimited
Microsoft Graph graph.microsoft.com 443 OAuth 2.0 Email notifications Tenant-based

Firewall Requirements:

  • Outbound HTTPS (443): Must be allowed for all external API calls
  • DNS Resolution: Must be allowed (port 53)
  • NTP: Recommended for time sync (port 123)

API Key Storage:

  • Location: /var/www/nordabiznes/.env (production)
  • Permissions: chmod 600 (owner read/write only)
  • Owner: maciejpi:maciejpi
  • ⚠️ NEVER commit to git! (in .gitignore)

Deployment Workflow

Rsync-Based Deployment (OVH VPS)

Production deployment uses rsync (no git on OVH VPS).

┌────────────────────────────────────────────────────────────────┐
│                     DEVELOPMENT (Mac)                          │
│                                                                │
│  • Code changes                                                │
│  • Local testing (localhost:5000)                              │
│  • Git commit                                                  │
└───────────────────────────┬────────────────────────────────────┘
                            │
                            │ git push origin master
                            │ git push inpi master
                            ▼
┌────────────────────────────────────────────────────────────────┐
│                   GIT REPOSITORIES                             │
│                                                                │
│  ┌─────────────────────────────────────────────────────┐      │
│  │  GITHUB @ github.com                                │      │
│  │  Repository: pienczyn/nordabiz                      │      │
│  │  Purpose: Cloud backup, collaboration              │      │
│  └─────────────────────────────────────────────────────┘      │
│                                                                │
│  ┌─────────────────────────────────────────────────────┐      │
│  │  GITEA @ r11-git-inpi (10.22.68.180:3000)           │      │
│  │  Repository: maciejpi/nordabiz                      │      │
│  │  Purpose: Internal backup, staging deploy          │      │
│  └─────────────────────────────────────────────────────┘      │
└───────────────────────────┬────────────────────────────────────┘
                            │
                            │ rsync (from dev Mac)
                            ▼
┌────────────────────────────────────────────────────────────────┐
│               PRODUCTION (OVH VPS 57.128.200.27)              │
│                                                                │
│  1. Deploy via rsync:                                          │
│     rsync -avz --exclude='.env' --exclude='venv/' \           │
│       ./ maciejpi@57.128.200.27:/var/www/nordabiznes/         │
│                                                                │
│  2. Restart service:                                           │
│     ssh maciejpi@57.128.200.27 \                               │
│       "sudo systemctl reload nordabiznes"                      │
│                                                                │
│  3. Verify deployment:                                         │
│     curl -I https://nordabiznes.pl/health                      │
└────────────────────────────────────────────────────────────────┘

Deployment Checklist:

  1. Code tested locally
  2. Git commit with descriptive message
  3. Push to both remotes (GitHub + Gitea)
  4. Rsync to OVH VPS
  5. Reload nordabiznes service
  6. Verify health endpoint (nordabiznes.pl)
  7. Check logs for errors (journalctl -u nordabiznes)
  8. Update release notes in app.py

Monitoring and Health Checks

Health Check Endpoint

Endpoint: GET /health Authentication: None (public) Purpose: Service availability monitoring

Response (Healthy):

HTTP/2 200 OK
Content-Type: application/json

{
  "status": "healthy",
  "timestamp": "2026-01-10T12:00:00Z",
  "database": "connected",
  "version": "1.0"
}

Health Check Commands:

# External check (via nginx)
curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 OK

# Localhost check (from OVH VPS)
ssh maciejpi@57.128.200.27 "curl -I http://localhost:5000/health"
# Expected: HTTP/1.1 200 OK

Service Status Checks

Application Service:

# Check systemd service
sudo systemctl status nordabiznes
# Active: active (running)

# Check process
ps aux | grep gunicorn
# Should show 5 processes (1 master + 4 workers)

# Check listening ports
ss -tlnp | grep :5000
# LISTEN 0 128 0.0.0.0:5000 0.0.0.0:*

Database Service:

# Check PostgreSQL service
sudo systemctl status postgresql
# Active: active (running)

# Check database connectivity
sudo -u postgres psql -c "SELECT version();"

# Check active connections
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='nordabiz';"

Nginx Service (OVH VPS):

# Check nginx status
ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"

# Check nginx logs
ssh maciejpi@57.128.200.27 "sudo tail -20 /var/log/nginx/error.log"

# Test nginx config
ssh maciejpi@57.128.200.27 "sudo nginx -t"

Planned Monitoring (Not Yet Implemented)

Zabbix Monitoring (Planned):

  • HTTPS endpoint monitoring (5-minute intervals)
  • Response time tracking
  • SSL certificate expiry alerts (30-day warning)
  • Service availability alerts
  • Database connection monitoring
  • Disk space monitoring

Backup and Disaster Recovery

PostgreSQL Database Backups

Current Status: Manual backups only Backup Location: /backup/nordabiz/ (on OVH VPS inpi-vps-waw01)

Manual Backup Procedure:

# Create backup
sudo -u postgres pg_dump nordabiz | gzip > \
  /backup/nordabiz/nordabiz_$(date +%Y%m%d_%H%M%S).sql.gz

# Verify backup
gunzip -c /backup/nordabiz/nordabiz_20260110_120000.sql.gz | head -n 20

Restore Procedure:

# Stop application
sudo systemctl stop nordabiznes

# Drop existing database (CAUTION!)
sudo -u postgres psql -c "DROP DATABASE nordabiz;"
sudo -u postgres psql -c "CREATE DATABASE nordabiz OWNER nordabiz_app;"

# Restore from backup
gunzip -c /backup/nordabiz/nordabiz_20260110_120000.sql.gz | \
  sudo -u postgres psql nordabiz

# Grant permissions
sudo -u postgres psql -d nordabiz -c "GRANT ALL ON ALL TABLES IN SCHEMA public TO nordabiz_app;"
sudo -u postgres psql -d nordabiz -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;"

# Restart application
sudo systemctl start nordabiznes

# Verify
curl -I http://localhost:5000/health

Planned: Automated daily backups via cron


OVH VPS Backups

OVH Automated Backups:

  • OVH VPS snapshot backups (configured via OVH panel)
  • Application code backed up via git (GitHub + Gitea)
  • Database backed up via pg_dump + offsite copy

Security Configuration

OVH VPS Firewall

The OVH VPS uses ufw (Uncomplicated Firewall) and/or OVH firewall:

Inbound Rules:

ALLOW 22/tcp    (SSH - key-based auth only)
ALLOW 80/tcp    (HTTP - redirects to HTTPS)
ALLOW 443/tcp   (HTTPS - production traffic)
DENY all other inbound

SSH Access Control

Allowed Users:

  • maciejpi - Administrator account (sudo access)

Authentication:

  • SSH key-based authentication (required)
  • Password authentication disabled

SSH Configuration:

# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers maciejpi

SSH Access:

ssh maciejpi@57.128.200.27

Database Security

PostgreSQL Access Control:

# postgresql.conf
listen_addresses = 'localhost'  # NO external connections!
port = 5432

# pg_hba.conf
local   nordabiz   nordabiz_app   md5       # Local socket
host    nordabiz   nordabiz_app   127.0.0.1/32   md5   # Localhost only

Connection Restrictions:

  • Localhost (127.0.0.1): Allowed
  • Internal network (10.22.68.0/24): Denied
  • External network: Denied

User Privileges:

-- nordabiz_app user (application)
GRANT CONNECT ON DATABASE nordabiz TO nordabiz_app;
GRANT USAGE ON SCHEMA public TO nordabiz_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nordabiz_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;

-- postgres user (admin)
-- Full privileges on all databases

Troubleshooting Guide

Common Issues and Solutions

Issue 1: ERR_TOO_MANY_REDIRECTS

Symptoms:

  • Browser shows "ERR_TOO_MANY_REDIRECTS"
  • Site inaccessible via https://nordabiznes.pl
  • Internal access (localhost:5000) works fine

Cause: NPM forwarding to port 80 instead of port 5000

Solution:

# 1. Verify NPM configuration
ssh maciejpi@10.22.68.250
docker exec nginx-proxy-manager_app_1 \
  sqlite3 /data/database.sqlite \
  "SELECT id, forward_host, forward_port FROM proxy_host WHERE id = 27;"

# 2. If forward_port != 5000, fix it via NPM Web UI:
# - Access http://10.22.68.250:81
# - Edit Proxy Host #27
# - Set Forward Port to 5000
# - Save

# 3. Verify fix
curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 OK

Prevention: Always verify forward_port = 5000 after NPM changes


Issue 2: 502 Bad Gateway

Symptoms:

  • NPM returns 502 Bad Gateway
  • External access fails
  • Internal access may or may not work

Possible Causes:

  1. Flask/Gunicorn service down
  2. Port 5000 not listening
  3. Network connectivity issue

Diagnosis:

# 1. Check Gunicorn service
ssh maciejpi@57.128.200.27
sudo systemctl status nordabiznes
# If not running: sudo systemctl start nordabiznes

# 2. Check port 5000 is listening
ss -tlnp | grep :5000
# Expected: LISTEN 0.0.0.0:5000

# 3. Test internal access
curl -I http://localhost:5000/health
# Expected: HTTP/1.1 200 OK

# 4. Test from NPM server
ssh maciejpi@10.22.68.250
curl -I http://57.128.200.27:5000/health
# Expected: HTTP/1.1 200 OK

Solution:

# Restart Gunicorn service
sudo systemctl restart nordabiznes

# Check logs for errors
sudo journalctl -u nordabiznes -n 50

Issue 3: Database Connection Errors

Symptoms:

  • Application shows "Database connection error"
  • Health endpoint returns error
  • Logs show "could not connect to server"

Diagnosis:

# 1. Check PostgreSQL service
sudo systemctl status postgresql
# If not running: sudo systemctl start postgresql

# 2. Test database connection
sudo -u postgres psql -c "SELECT 1;"
# Expected: 1

# 3. Check application database access
sudo -u maciejpi psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;"
# Expected: 1 (if password prompt, check .env)

# 4. Check active connections
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='nordabiz';"

Solution:

# Restart PostgreSQL
sudo systemctl restart postgresql

# Verify connection string in .env
cat /var/www/nordabiznes/.env | grep DATABASE_URL
# Expected: postgresql://nordabiz_app:PASSWORD@localhost:5432/nordabiz

Issue 4: SSL Certificate Expired

Symptoms:

  • Browser shows "Your connection is not private"
  • SSL certificate warning
  • Cert expired error

Diagnosis:

# Check certificate expiry
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl < /dev/null 2>/dev/null | \
  openssl x509 -noout -dates

# Check NPM logs for renewal errors
docker logs nginx-proxy-manager_app_1 | grep -i letsencrypt

Solution:

# Manual certificate renewal via NPM
# 1. Access NPM Web UI: http://10.22.68.250:81
# 2. Go to SSL Certificates
# 3. Find certificate ID 27
# 4. Click "Renew" button

# OR restart NPM to trigger auto-renewal
docker restart nginx-proxy-manager_app_1

Architecture Documentation

Data Flow Documentation

Incident Documentation

Infrastructure Documentation


Maintenance Notes

When to Update This Diagram

✏️ UPDATE when:

  • New server added to infrastructure
  • IP address changes
  • Port configuration changes
  • Network topology changes (new firewall rules, NAT changes)
  • SSL/TLS configuration updates
  • Deployment procedure changes
  • Critical configuration changes (like NPM proxy settings)

DON'T UPDATE for:

  • Application code changes (not infrastructure)
  • Database schema changes (covered in database diagram)
  • New Flask routes (covered in component diagram)
  • Dependency updates (same infrastructure)

Review Frequency

  • After incidents: Immediate review and update
  • After infrastructure changes: Within 24 hours
  • Quarterly: Verify accuracy against actual configuration
  • Before major deployments: Review deployment section

Verification Checklist

  • All IP addresses are current
  • All port numbers are correct
  • Nginx proxy_pass configuration verified (127.0.0.1:5000)
  • DNS records match actual configuration
  • SSL certificate status checked
  • Firewall rules documented accurately
  • Service status verified on all servers
  • Health check endpoints tested
  • Backup procedures tested

Glossary

Term Definition
NPM Nginx Proxy Manager - Docker-based reverse proxy with web UI
NAT Network Address Translation - maps public IP to internal IPs
SSL Termination Decrypting HTTPS at proxy, forwarding HTTP to backend
Fortigate Enterprise firewall/router device
Gunicorn Python WSGI HTTP server for running Flask applications
Let's Encrypt Free automated SSL/TLS certificate authority
HSTS HTTP Strict Transport Security - forces HTTPS
Proxmox VE Virtualization platform for managing VMs
Systemd Linux init system and service manager
WSGI Web Server Gateway Interface - Python web server standard
VM Virtual Machine
vCPU Virtual CPU core
ORM Object-Relational Mapping (SQLAlchemy)

Document Status: Complete Diagram Validated: 2026-04-04 Production Verified: 2026-04-04 (OVH VPS migration) Mermaid Syntax: v10.6+ Renders in: GitHub, GitLab, VS Code (with Mermaid extension)


Production traffic flow:

Internet → Nginx (57.128.200.27:443) → Gunicorn (127.0.0.1:5000)

Verify production:

curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 OK