# 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
```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 (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:**
```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:** `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
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 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:**
```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
```