- 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>
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:
- ✅ Code tested locally
- ✅ Git commit with descriptive message
- ✅ Push to both remotes (GitHub + Gitea)
- ✅ Rsync to OVH VPS
- ✅ Reload
nordabiznesservice - ✅ Verify health endpoint (nordabiznes.pl)
- ✅ Check logs for errors (
journalctl -u nordabiznes) - ✅ 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:
- Flask/Gunicorn service down
- Port 5000 not listening
- 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
Related Documentation
Architecture Documentation
- System Context Diagram - High-level system overview
- Container Diagram - Major containers and technology stack
- Flask Application Components - Internal application structure (planned)
- Database Schema - Entity relationships (planned)
- Network Topology - Network diagram (planned)
Data Flow Documentation
- HTTP Request Flow - Detailed HTTP request path (planned)
- Authentication Flow - User login sequence (planned)
Incident Documentation
- Incident Report 2026-01-02 - ERR_TOO_MANY_REDIRECTS incident
Infrastructure Documentation
- CLAUDE.md - Complete project documentation
- Infrastructure Analysis - 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:
curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 OK