# Deployment Architecture Diagram **Document Version:** 1.0 **Last Updated:** 2026-04-04 **Status:** Production LIVE **Diagram Type:** Infrastructure / Deployment View --- > **UWAGA (2026-04-04):** Produkcja przeniesiona z OVH VPS inpi-vps-waw01 (VM 249, 57.128.200.27) na **OVH VPS (57.128.200.27, hostname inpi-vps-waw01)**. Diagramy Mermaid poniżej odzwierciedlają starą architekturę — faktyczny stan to: DNS nordabiznes.pl -> 57.128.200.27 (bezpośrednio, bez NPM). NPM (10.22.68.250) obsługuje tylko staging. ## 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 ```mermaid graph TB %% External actors and entry points Internet["🌐 INTERNET
External Users"] %% Production server (OVH VPS) subgraph "OVH VPS [inpi-vps-waw01 | 57.128.200.27]" subgraph "Reverse Proxy" Nginx["🔒 NGINX
Ports: 443 (HTTPS), 80 (redirect)

SSL: Let's Encrypt (certbot)
Domains: nordabiznes.pl"] end subgraph "Application Layer" Gunicorn["🌐 GUNICORN + FLASK
Port: 127.0.0.1:5000
Workers: 4
User: maciejpi

App: /var/www/nordabiznes
Service: nordabiznes.service
Timeout: 120s"] end subgraph "Data Layer" PostgreSQL["💾 POSTGRESQL 14
Port: 5432
Listen: 127.0.0.1 ONLY

Database: nordabiz
User: nordabiz_app
Tables: 36

No external connections!"] end subgraph "Background Jobs" Scripts["⚙️ PYTHON SCRIPTS
Execution: Cron / Manual

• seo_audit.py
• social_media_audit.py
• gbp_audit.py
• fetch_company_news.py"] end end %% External services subgraph "External APIs (HTTPS)" Gemini["🤖 Google Gemini AI
generativelanguage.googleapis.com
Auth: API Key"] BraveAPI["🔍 Brave Search API
api.search.brave.com
Auth: API Key"] PageSpeed["📊 Google PageSpeed API
googleapis.com/pagespeedonline
Auth: API Key"] Places["📍 Google Places API
maps.googleapis.com/maps/api
Auth: API Key"] KRS["🏛️ KRS Open API
api-krs.ms.gov.pl
Auth: Public"] MSGraph["📧 Microsoft Graph API
graph.microsoft.com
Auth: OAuth 2.0"] end %% User traffic flow Internet -->|"HTTPS :443
HTTP :80"| Nginx %% Nginx to backend Nginx -->|"HTTP
127.0.0.1:5000"| Gunicorn %% Application to database (localhost only) Gunicorn <-->|"SQL
localhost:5432
SQLAlchemy ORM"| PostgreSQL Scripts <-->|"SQL
127.0.0.1:5432
Direct connection"| PostgreSQL %% External API calls Gunicorn -->|"HTTPS
API calls"| Gemini Gunicorn -->|"HTTPS
API calls"| BraveAPI Gunicorn -->|"HTTPS
API calls"| Places Gunicorn -->|"HTTPS
API calls"| KRS Gunicorn -->|"HTTPS
OAuth 2.0"| MSGraph Scripts -->|"HTTPS
Audit requests"| PageSpeed Scripts -->|"HTTPS
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 (57.128.200.27) 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:** ```bash # 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:** ```bash ssh maciejpi@57.128.200.27 # CRITICAL: Always use 'maciejpi' user, NEVER 'root'! ``` **Gunicorn Configuration:** ```ini # /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:** ```nginx # /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:** ```bash # 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):** ```http Strict-Transport-Security: max-age=31536000; includeSubDomains X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block ``` **Certificate Verification:** ```bash # 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:** `www-data:www-data` - **⚠️ 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 HTTP/2 200 OK Content-Type: application/json { "status": "healthy", "timestamp": "2026-01-10T12:00:00Z", "database": "connected", "version": "1.0" } ``` **Health Check Commands:** ```bash # 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:** ```bash # 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:** ```bash # 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):** ```bash # 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:** ```bash # 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:** ```bash # 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:** ```bash # /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes AllowUsers maciejpi ``` **SSH Access:** ```bash ssh maciejpi@57.128.200.27 ``` ### Database Security **PostgreSQL Access Control:** ```conf # 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:** ```sql -- 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:** ```bash # 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:** ```bash # 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:** ```bash # 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:** ```bash # 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 www-data 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:** ```bash # 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:** ```bash # 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:** ```bash # 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 ``` --- ## Related Documentation ### Architecture Documentation - **[System Context Diagram](./01-system-context.md)** - High-level system overview - **[Container Diagram](./02-container-diagram.md)** - Major containers and technology stack - **[Flask Application Components](./04-flask-components.md)** - Internal application structure (planned) - **[Database Schema](./05-database-schema.md)** - Entity relationships (planned) - **[Network Topology](./07-network-topology.md)** - Network diagram (planned) ### Data Flow Documentation - **[HTTP Request Flow](./flows/06-http-request-flow.md)** - Detailed HTTP request path (planned) - **[Authentication Flow](./flows/01-authentication-flow.md)** - User login sequence (planned) ### Incident Documentation - **[Incident Report 2026-01-02](../INCIDENT_REPORT_20260102.md)** - ERR_TOO_MANY_REDIRECTS incident ### Infrastructure Documentation - **[CLAUDE.md](../../CLAUDE.md)** - Complete project documentation - **[Infrastructure Analysis](./.auto-claude/specs/003-.../analysis/infrastructure-components.md)** - Detailed infrastructure analysis (internal) --- ## 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:** ```bash curl -I https://nordabiznes.pl/health # Expected: HTTP/2 200 OK ```