nordabiz/docs/architecture/07-network-topology.md
Maciej Pienczyn fa4fb92390 docs: Add complete architecture documentation with C4 diagrams
- System Context diagram (C4 Level 1)
- Container diagram (C4 Level 2)
- Flask component diagram (C4 Level 3)
- Deployment architecture with NPM proxy
- Database schema (PostgreSQL)
- External integrations (Gemini AI, Brave Search, PageSpeed)
- Network topology (INPI infrastructure)
- Security architecture
- API endpoints reference
- Troubleshooting guide
- Data flow diagrams (auth, search, AI chat, SEO audit, news monitoring)

All diagrams use Mermaid.js and render automatically on GitHub.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 12:40:52 +01:00

37 KiB

Network Topology Diagram

Document Version: 1.0 Last Updated: 2026-01-10 Status: Production LIVE Diagram Type: Network Topology / Infrastructure Network


Overview

This document provides a network-centric view of the Norda Biznes Hub infrastructure. It focuses on:

  • Network layout and zones (Public Internet, DMZ, Application Zone, Data Zone)
  • Fortigate firewall configuration with NAT rules and port forwarding
  • Internal network topology (10.22.68.0/24 subnet)
  • Network routing and traffic flows
  • DNS configuration (external and internal)
  • Network security boundaries and firewall rules
  • IP addressing scheme and port mappings

Abstraction Level: Network Infrastructure Audience: Network Engineers, Security Team, DevOps, System Administrators Purpose: Understanding network architecture, firewall configuration, routing, security boundaries, incident response

Related Documentation:


Network Topology Diagram

graph TB
    %% External network
    subgraph "Public Internet (Untrusted)"
        Users["👥 External Users<br/>Website Visitors"]
        DNS_OVH["🌐 OVH DNS<br/>nordabiznes.pl<br/>→ 85.237.177.83"]
    end

    %% Perimeter security
    subgraph "Network Perimeter"
        Fortigate["🛡️ FORTIGATE FIREWALL<br/><br/>WAN: 85.237.177.83<br/>LAN: 10.22.68.1<br/><br/>NAT Rules:<br/>• 85.237.177.83:443 → 10.22.68.250:443<br/>• 85.237.177.83:80 → 10.22.68.250:80<br/><br/>Firewall Policy:<br/>• Allow HTTPS (443) from ANY<br/>• Allow HTTP (80) from ANY<br/>• Allow SSH (22) from ADMIN_NET<br/>• Default: DENY ALL"]
    end

    %% Internal network zones
    subgraph "INPI Internal Network (10.22.68.0/24)"

        %% DMZ Zone
        subgraph "DMZ Zone (Semi-Trusted)"
            NPM_Server["🖥️ R11-REVPROXY-01<br/><br/>IP: 10.22.68.250<br/>VM ID: 119<br/>Hostname: r11-revproxy-01<br/><br/>Services:<br/>• NPM (Docker) :443, :80, :81<br/>• SSH :22<br/><br/>Gateway: 10.22.68.1<br/>DNS: 10.22.68.1"]
        end

        %% Application Zone
        subgraph "Application Zone (Trusted)"
            App_Server["🖥️ NORDABIZ-01<br/><br/>IP: 10.22.68.249<br/>VM ID: 249<br/>Hostname: nordabiz-01<br/>DNS: nordabiznes.inpi.local<br/><br/>Services:<br/>• Flask/Gunicorn :5000<br/>• PostgreSQL :5432 (localhost)<br/>• Nginx :80, :443 (system)<br/>• SSH :22<br/><br/>Gateway: 10.22.68.1<br/>DNS: 10.22.68.1"]
        end

        %% Internal Services Zone
        subgraph "Internal Services Zone (Trusted)"
            Git_Server["🖥️ r11-git-inpi<br/><br/>IP: 10.22.68.180<br/>Hostname: r11-git-inpi<br/><br/>Services:<br/>• Gitea :3000 (HTTPS)<br/>• SSH :22<br/><br/>Gateway: 10.22.68.1"]
        end

        %% Internal DNS
        Internal_DNS["🔍 Internal DNS<br/>Zone: inpi.local<br/><br/>Records:<br/>• nordabiznes.inpi.local → 10.22.68.249<br/>• git.inpi.local → 10.22.68.180"]
    end

    %% External services
    subgraph "External APIs (Internet)"
        API_Google["☁️ Google Cloud APIs<br/><br/>• Gemini AI API<br/>• PageSpeed Insights API<br/>• Places API<br/><br/>Auth: API Keys<br/>HTTPS only"]

        API_MS["☁️ Microsoft Graph API<br/><br/>• Email sending<br/><br/>Auth: OAuth 2.0<br/>HTTPS only"]

        API_Brave["☁️ Brave Search API<br/><br/>• News search<br/><br/>Auth: API Key<br/>HTTPS only"]

        API_KRS["🏛️ KRS Open API<br/><br/>• Company registry<br/><br/>Auth: Public<br/>HTTPS only"]

        Web_Scrapers["🌐 Web Scraping<br/><br/>• ALEO.com (NIP)<br/>• rejestr.io (Connections)<br/><br/>HTTPS only"]
    end

    %% Network flows - User traffic
    Users -->|"DNS Query<br/>nordabiznes.pl"| DNS_OVH
    DNS_OVH -->|"DNS Response<br/>85.237.177.83"| Users
    Users -->|"HTTPS :443<br/>HTTP :80"| Fortigate

    %% Firewall to DMZ
    Fortigate -->|"NAT + Route<br/>:443 → 10.22.68.250:443<br/>:80 → 10.22.68.250:80"| NPM_Server

    %% DMZ to Application Zone
    NPM_Server ==>|"⚠️ CRITICAL<br/>HTTP :5000<br/>(NOT port 80!)"| App_Server
    NPM_Server -.->|"Internal DNS Query<br/>nordabiznes.inpi.local"| Internal_DNS

    %% Application to External APIs
    App_Server -->|"HTTPS<br/>API Requests"| API_Google
    App_Server -->|"HTTPS<br/>OAuth 2.0"| API_MS
    App_Server -->|"HTTPS<br/>API Requests"| API_Brave
    App_Server -->|"HTTPS<br/>API Requests"| API_KRS
    App_Server -->|"HTTPS<br/>Web Scraping"| Web_Scrapers

    %% Git deployment
    App_Server -.->|"git pull<br/>HTTPS :3000<br/>Deployment only"| Git_Server

    %% Admin SSH access
    Fortigate -.->|"SSH :22<br/>Admin only"| NPM_Server
    Fortigate -.->|"SSH :22<br/>Admin only"| App_Server
    Fortigate -.->|"SSH :22<br/>Admin only"| Git_Server

    %% Styling
    classDef public fill:#f9f,stroke:#333,stroke-width:2px
    classDef perimeter fill:#f96,stroke:#333,stroke-width:3px
    classDef dmz fill:#ff9,stroke:#333,stroke-width:2px
    classDef app fill:#9f9,stroke:#333,stroke-width:2px
    classDef internal fill:#99f,stroke:#333,stroke-width:2px
    classDef external fill:#ccc,stroke:#333,stroke-width:1px

    class Users,DNS_OVH public
    class Fortigate perimeter
    class NPM_Server dmz
    class App_Server app
    class Git_Server,Internal_DNS internal
    class API_Google,API_MS,API_Brave,API_KRS,Web_Scrapers external

Network Zones

Zone 1: Public Internet (Untrusted)

Purpose: External user access and public DNS resolution

Components:

  • Website visitors (anonymous and authenticated users)
  • OVH DNS servers (authoritative for nordabiznes.pl)

Security Level: Untrusted - No trust, all traffic inspected at perimeter

Access:

  • HTTPS (443) - Public website access
  • HTTP (80) - Redirects to HTTPS

Protection:

  • Fortigate firewall inspection
  • Rate limiting at application layer
  • DDoS protection (via OVH)

Zone 2: Network Perimeter (Firewall)

Purpose: Network security boundary, NAT, and traffic filtering

Components:

  • Fortigate Firewall
    • Model: (Infrastructure-specific)
    • WAN IP: 85.237.177.83
    • LAN IP: 10.22.68.1 (gateway for internal network)

Security Level: Perimeter - First line of defense

NAT Configuration:

Public Address Public Port Internal Address Internal Port Protocol Purpose
85.237.177.83 443 10.22.68.250 443 TCP HTTPS to NPM
85.237.177.83 80 10.22.68.250 80 TCP HTTP to NPM (redirect)

Firewall Rules:

Priority Source Destination Port Action Purpose
1 ANY 85.237.177.83 443 ALLOW Public HTTPS access
2 ANY 85.237.177.83 80 ALLOW HTTP (redirect to HTTPS)
3 ADMIN_NET 10.22.68.0/24 22 ALLOW SSH administration
4 10.22.68.0/24 ANY 443 ALLOW Outbound HTTPS (APIs)
5 10.22.68.0/24 ANY 80 ALLOW Outbound HTTP (fallback)
99 ANY ANY ANY DENY Default deny all

Notes:

  • Stateful firewall maintains connection state
  • Return traffic automatically allowed for established connections
  • No inbound SSH from public internet (admin access via internal network only)

Zone 3: DMZ Zone (Semi-Trusted)

Purpose: SSL termination, reverse proxy, and public-facing services

Network: 10.22.68.250/32 (single host)

Components:

  • R11-REVPROXY-01 (VM 119)
    • IP: 10.22.68.250
    • Services: Nginx Proxy Manager (Docker), SSH

Security Level: Semi-Trusted - Exposed to internet traffic, hardened configuration

Inbound Traffic:

  • From Internet (via Fortigate NAT): HTTPS :443, HTTP :80
  • From ADMIN_NET: SSH :22

Outbound Traffic:

  • To Application Zone (10.22.68.249): HTTP :5000 (Flask/Gunicorn)
  • To Internet: HTTPS (for Let's Encrypt ACME challenge, Docker image updates)

Security Controls:

  • SSL/TLS termination (Let's Encrypt certificates)
  • HTTP to HTTPS redirection
  • Minimal attack surface (only NPM + SSH)
  • Regular security updates
  • Docker container isolation

⚠️ CRITICAL CONFIGURATION:

# NPM Proxy Host Configuration (Host ID: 27)
domain_names:
  - nordabiznes.pl
forward_host: 10.22.68.249
forward_port: 5000  # ⚠️ MUST be 5000, NOT 80!
ssl:
  certificate_id: 27
  force_ssl: true
  http2_support: true
  hsts_enabled: true

Common Misconfiguration:

# ❌ WRONG - Causes infinite redirect loop
forward_port: 80  # This forwards to nginx on NORDABIZ-01, which redirects to HTTPS

Correct Configuration:

# ✅ CORRECT - Forwards directly to Flask/Gunicorn
forward_port: 5000  # Direct connection to application server

Verification:

# Test from external network
curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 (success)

# Test internal routing (from NPM server)
curl -I http://10.22.68.249:5000/health
# Expected: HTTP/1.1 200 (success)

Incident Reference: See INCIDENT_REPORT_20260102.md for port 80 vs 5000 misconfiguration incident.


Zone 4: Application Zone (Trusted)

Purpose: Application hosting, business logic processing

Network: 10.22.68.249/32 (single host)

Components:

  • NORDABIZ-01 (VM 249)
    • IP: 10.22.68.249
    • Internal DNS: nordabiznes.inpi.local
    • Services: Flask/Gunicorn :5000, PostgreSQL :5432 (localhost), Nginx :80/443 (unused), SSH :22

Security Level: Trusted - Internal zone, application processing

Inbound Traffic:

  • From DMZ (10.22.68.250): HTTP :5000 (NPM → Gunicorn)
  • From ADMIN_NET: SSH :22

Outbound Traffic:

  • To External APIs: HTTPS :443 (Google, Microsoft, Brave, KRS)
  • To Git Server (10.22.68.180): HTTPS :3000 (deployment)
  • To Internet: HTTP/HTTPS (web scraping ALEO.com, rejestr.io)

Localhost Services (127.0.0.1):

  • PostgreSQL :5432
    • Listen address: 127.0.0.1 ONLY
    • No external connections allowed
    • Access via Unix socket or localhost TCP
    • Users: nordabiz_app (application), maciejpi (admin)

Security Controls:

  • PostgreSQL confined to localhost (critical data protection)
  • Flask/Gunicorn behind reverse proxy (no direct internet exposure)
  • Application-level security (CSRF, authentication, rate limiting)
  • Regular security updates
  • SSH key-based authentication only

Network Configuration:

IP Address: 10.22.68.249/24
Gateway: 10.22.68.1
DNS: 10.22.68.1

Service Ports:

Port Service Listen Address Access Purpose
5000 Gunicorn 0.0.0.0 Internal network Flask application
5432 PostgreSQL 127.0.0.1 Localhost only Database
80 Nginx 0.0.0.0 Internal network HTTP→HTTPS redirect (unused)
443 Nginx 0.0.0.0 Internal network SSL (unused in prod)
22 SSH 0.0.0.0 Admin network Remote administration

Zone 5: Internal Services Zone (Trusted)

Purpose: Internal development and deployment tools

Network: 10.22.68.180/32 (single host)

Components:

  • r11-git-inpi (Git Server)
    • IP: 10.22.68.180
    • Services: Gitea :3000 (HTTPS), SSH :22

Security Level: Trusted - Internal services, no public exposure

Inbound Traffic:

  • From Application Zone (10.22.68.249): HTTPS :3000 (git pull for deployment)
  • From ADMIN_NET: SSH :22, HTTPS :3000 (git operations)

Outbound Traffic:

  • Minimal (software updates)

Security Controls:

  • No direct internet access from public
  • Self-signed SSL certificate (internal use)
  • SSH key-based authentication
  • Repository access control (users: maciejpi, gitadmin)

Git Configuration:


Zone 6: External APIs (Internet)

Purpose: Third-party service integration

Components:

  • Google Cloud APIs (Gemini AI, PageSpeed, Places)
  • Microsoft Graph API (Email)
  • Brave Search API (News)
  • KRS Open API (Company registry)
  • Web scraping targets (ALEO.com, rejestr.io)

Security Level: External - Outside our control

Access Pattern:

  • Application Zone initiates outbound HTTPS connections
  • API key authentication (stored in .env, never committed)
  • OAuth 2.0 for Microsoft Graph
  • Rate limiting and quota management

Network Flow:

App Server (10.22.68.249)
  → Fortigate (10.22.68.1)
  → Internet Gateway
  → External API (HTTPS :443)

Security Controls:

  • HTTPS/TLS 1.2+ enforced
  • API keys in environment variables
  • Rate limiting to prevent quota exhaustion
  • Error handling and retry logic
  • Cost tracking and monitoring

IP Addressing Scheme

Public IP Addresses

IP Address Type Purpose Owner
85.237.177.83 Static Public Fortigate WAN interface INPI Infrastructure

Internal IP Addresses (10.22.68.0/24)

IP Address Hostname VM ID Purpose Zone
10.22.68.1 fortigate-lan N/A Default gateway Perimeter
10.22.68.180 r11-git-inpi N/A Gitea server Internal Services
10.22.68.249 nordabiz-01 249 Application + DB server Application
10.22.68.250 r11-revproxy-01 119 NPM reverse proxy DMZ

Reserved Ranges

Range Purpose Status
10.22.68.1-10.22.68.99 Infrastructure services Reserved
10.22.68.100-10.22.68.199 Application servers Available
10.22.68.200-10.22.68.254 Future expansion Available

Port Mapping Reference

Complete Port Matrix

Service Server IP Port Protocol Access Purpose
Public-Facing
HTTPS NPM 10.22.68.250 443 TCP Public (via NAT) SSL termination
HTTP NPM 10.22.68.250 80 TCP Public (via NAT) Redirect to HTTPS
Application Services
Flask/Gunicorn NORDABIZ-01 10.22.68.249 5000 TCP Internal only Web application
PostgreSQL NORDABIZ-01 127.0.0.1 5432 TCP Localhost only Database
Nginx (unused) NORDABIZ-01 10.22.68.249 80 TCP Internal only HTTP redirect (not used)
Nginx (unused) NORDABIZ-01 10.22.68.249 443 TCP Internal only SSL (not used)
Internal Services
Gitea r11-git-inpi 10.22.68.180 3000 TCP Internal only Git repository
NPM Admin NPM 10.22.68.250 81 TCP Internal only NPM web UI
Administration
SSH NORDABIZ-01 10.22.68.249 22 TCP Admin network Remote admin
SSH NPM 10.22.68.250 22 TCP Admin network Remote admin
SSH r11-git-inpi 10.22.68.180 22 TCP Admin network Remote admin

Critical Port Forwarding (NPM → Application)

⚠️ MOST CRITICAL CONFIGURATION:

Source Destination Protocol Purpose Notes
NPM :443 10.22.68.249:5000 HTTP HTTPS requests → Flask CORRECT
NPM :80 10.22.68.249:5000 HTTP HTTP requests → Flask CORRECT
NPM :443 10.22.68.249:80 HTTP HTTPS requests → Nginx WRONG - Redirect loop!

Why Port 5000 is Critical:

  1. NPM terminates SSL at port 443
  2. NPM forwards decrypted HTTP to backend
  3. Backend must accept HTTP (not redirect to HTTPS)
  4. Flask/Gunicorn on port 5000 accepts HTTP
  5. Nginx on port 80 redirects HTTP → HTTPS (creates loop)

Incident History:

  • 2026-01-02: Misconfiguration set NPM to forward to port 80
  • Result: ERR_TOO_MANY_REDIRECTS (infinite loop)
  • Root Cause: Nginx on port 80 redirects to HTTPS, NPM receives HTTPS, forwards to Nginx, repeat
  • Fix: Changed NPM forward port from 80 to 5000
  • Prevention: Documentation, monitoring, configuration backups

DNS Configuration

External DNS (Public)

Provider: OVH DNS Domain: nordabiznes.pl Nameservers: (OVH default nameservers)

DNS Records:

Record Type Name Value TTL Purpose
A nordabiznes.pl 85.237.177.83 3600 Main website
A www.nordabiznes.pl 85.237.177.83 3600 WWW subdomain
MX nordabiznes.pl (Not configured) - No email hosting
TXT nordabiznes.pl (SPF, DKIM if configured) - Email authentication

DNS Propagation:

# Check DNS resolution
dig nordabiznes.pl +short
# Expected: 85.237.177.83

# Check from Google DNS
dig @8.8.8.8 nordabiznes.pl +short
# Expected: 85.237.177.83

# Check WHOIS
whois nordabiznes.pl

Internal DNS (Private)

Provider: INPI Internal DNS Domain: inpi.local DNS Server: 10.22.68.1 (Fortigate or internal DNS server)

DNS Records:

Record Type Name Value Purpose
A nordabiznes.inpi.local 10.22.68.249 Application server
A git.inpi.local 10.22.68.180 Gitea server
A npm.inpi.local 10.22.68.250 NPM server

DNS Resolution Flow:

1. Application needs to resolve external domain (e.g., generativelanguage.googleapis.com)
   → Queries internal DNS (10.22.68.1)
   → Internal DNS forwards to public DNS (8.8.8.8)
   → Returns public IP

2. Application needs to resolve internal domain (e.g., git.inpi.local)
   → Queries internal DNS (10.22.68.1)
   → Internal DNS returns 10.22.68.180
   → No external query

Configuration:

# /etc/resolv.conf on NORDABIZ-01
nameserver 10.22.68.1
search inpi.local

Network Routing

Default Gateway

All internal servers use 10.22.68.1 (Fortigate LAN interface) as default gateway.

Routing Table (NORDABIZ-01 Example)

# ip route show
default via 10.22.68.1 dev ens18  # All non-local traffic → Fortigate
10.22.68.0/24 dev ens18 proto kernel scope link src 10.22.68.249  # Local subnet
127.0.0.0/8 dev lo  # Localhost

Network Flow Examples

Example 1: User Accesses Website

User Browser (Internet)
  ↓ DNS query
OVH DNS: nordabiznes.pl → 85.237.177.83
  ↓ HTTPS :443
Fortigate WAN (85.237.177.83:443)
  ↓ NAT translation
Fortigate LAN → NPM (10.22.68.250:443)
  ↓ SSL termination, proxy
NPM → Flask/Gunicorn (10.22.68.249:5000)
  ↓ HTTP request processing
Flask → PostgreSQL (127.0.0.1:5432)
  ↓ SQL query
PostgreSQL → Flask (result set)
  ↓ HTML rendering
Flask → NPM (HTTP response)
  ↓ SSL encryption
NPM → Fortigate LAN (HTTPS)
  ↓ NAT reverse
Fortigate WAN → User Browser (85.237.177.83:443)

Total Hops: 6 (external) + 3 (internal) = 9 hops Typical Latency: 150-250ms total

Example 2: Application Calls External API (Gemini AI)

Flask Application (10.22.68.249)
  ↓ HTTPS :443
Default Gateway (10.22.68.1)
  ↓ NAT + Firewall
Fortigate WAN → Internet
  ↓ Route to Google
Google Gemini API (generativelanguage.googleapis.com)
  ↓ API response
Internet → Fortigate WAN
  ↓ NAT reverse
Fortigate LAN → Flask Application (10.22.68.249)

Total Hops: 3 (internal) + variable (internet) + 2 (return) ≈ 15-25 hops Typical Latency: 200-500ms (depends on API)

Example 3: Git Pull for Deployment

Flask Application (10.22.68.249)
  ↓ git pull over HTTPS :3000
Local routing (same subnet)
  ↓ Direct connection
Gitea Server (10.22.68.180:3000)
  ↓ Repository data
Gitea → Flask Application

Total Hops: 1 (direct local subnet) Typical Latency: <5ms


Network Security Boundaries

Trust Boundaries

┌─────────────────────────────────────────────────────────────┐
│ PUBLIC INTERNET (Untrusted)                                 │
│ • Unknown source IPs                                        │
│ • Potential attack vectors                                  │
│ • DDoS, injection, reconnaissance                           │
└────────────────────────┬────────────────────────────────────┘
                         ▼
            ╔════════════════════════╗
            ║ FORTIGATE FIREWALL     ║ ◄── Trust Boundary #1
            ║ • Stateful inspection  ║
            ║ • NAT translation      ║
            ║ • Rate limiting        ║
            ╚════════════════════════╝
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ DMZ ZONE (Semi-Trusted)                                     │
│ • NPM reverse proxy                                         │
│ • SSL termination                                           │
│ • Limited attack surface                                    │
└────────────────────────┬────────────────────────────────────┘
                         ▼
            ╔════════════════════════╗
            ║ NPM PROXY FILTERING    ║ ◄── Trust Boundary #2
            ║ • Domain validation    ║
            ║ • Header inspection    ║
            ║ • Access logging       ║
            ╚════════════════════════╝
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION ZONE (Trusted)                                  │
│ • Flask/Gunicorn application                                │
│ • Business logic processing                                 │
│ • Authentication & authorization                            │
└────────────────────────┬────────────────────────────────────┘
                         ▼
            ╔════════════════════════╗
            ║ APP-LEVEL SECURITY     ║ ◄── Trust Boundary #3
            ║ • @login_required      ║
            ║ • CSRF protection      ║
            ║ • Input sanitization   ║
            ╚════════════════════════╝
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ DATA ZONE (Critical)                                        │
│ • PostgreSQL database                                       │
│ • Localhost-only access                                     │
│ • Encrypted at rest (planned)                               │
└─────────────────────────────────────────────────────────────┘

Firewall Rules Summary

Source Zone Destination Zone Allowed Protocols Purpose
Internet DMZ HTTPS (443), HTTP (80) Public website access
DMZ Application HTTP (5000) Reverse proxy to app
Application Data (localhost) PostgreSQL (5432) Database queries
Application Internet HTTPS (443) External API calls
Application Internal Services HTTPS (3000), SSH (22) Git operations
Admin Network All Zones SSH (22) Remote administration

Defense in Depth Layers

  1. Perimeter Security (Fortigate)

    • Firewall rules
    • NAT
    • DDoS protection
  2. DMZ Security (NPM)

    • SSL/TLS termination
    • Reverse proxy filtering
    • Access logging
  3. Application Security (Flask)

    • Authentication (Flask-Login)
    • Authorization (@login_required decorators)
    • CSRF protection (Flask-WTF)
    • Input validation
    • Rate limiting (Flask-Limiter)
  4. Data Security (PostgreSQL)

    • Localhost-only access
    • Password authentication
    • SQL injection prevention (SQLAlchemy ORM)
    • Connection pooling limits

Network Monitoring

Health Checks

External Health Check:

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

# Check SSL certificate
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl
# Expected: Let's Encrypt certificate, valid dates

Internal Health Checks:

# From NORDABIZ-01, check Gunicorn
curl -I http://127.0.0.1:5000/health
# Expected: HTTP/1.1 200 OK

# Check PostgreSQL
sudo -u postgres psql -c "SELECT version();"
# Expected: PostgreSQL version information

# Check network connectivity
ping -c 3 10.22.68.1  # Gateway
ping -c 3 8.8.8.8     # Internet

Network Performance Metrics

Typical Latency:

  • Internal subnet (10.22.68.x → 10.22.68.y): <2ms
  • Localhost services (127.0.0.1): <1ms
  • Internet egress (to Google APIs): 20-50ms
  • End-to-end user request: 150-250ms

Bandwidth:

  • Internal network: 1 Gbps
  • Internet connection: (Infrastructure-dependent)
  • NPM → App throughput: ~500 Mbps typical

Connection Limits:

  • Gunicorn workers: 4 (handles ~400 concurrent connections)
  • PostgreSQL max_connections: 100
  • Nginx (NPM) connections: Unlimited (memory-limited)

Monitoring Tools

Network Monitoring:

# Monitor network traffic
sudo tcpdump -i ens18 port 5000  # Watch Gunicorn traffic
sudo tcpdump -i ens18 port 443   # Watch HTTPS traffic

# Monitor active connections
sudo netstat -tulpn  # All listening services
sudo ss -s           # Socket statistics

# Monitor bandwidth
sudo iftop -i ens18  # Real-time bandwidth usage

Log Locations:

  • NPM logs: Docker container logs (docker logs nginx-proxy-manager)
  • Flask logs: /var/log/nordabiznes/app.log
  • Nginx (system) logs: /var/log/nginx/access.log, /var/log/nginx/error.log
  • PostgreSQL logs: /var/log/postgresql/postgresql-14-main.log
  • System logs: journalctl -u nordabiznes -f

Network Troubleshooting

Common Network Issues

Issue 1: Cannot Access Website (ERR_CONNECTION_REFUSED)

Symptoms:

  • Browser shows "ERR_CONNECTION_REFUSED"
  • curl https://nordabiznes.pl fails

Diagnosis:

# Check if Fortigate NAT is working
ping 85.237.177.83
# If fails: Network/ISP issue or Fortigate down

# Check if NPM is responding
curl http://10.22.68.250:443
# If fails: NPM service down

# Check if Gunicorn is responding
curl http://10.22.68.249:5000/health
# If fails: Gunicorn service down

Resolution:

  1. Restart NPM: docker restart nginx-proxy-manager (on R11-REVPROXY-01)
  2. Restart Gunicorn: sudo systemctl restart nordabiznes (on NORDABIZ-01)
  3. Check Fortigate firewall rules (requires admin access)

Issue 2: SSL Certificate Errors

Symptoms:

  • Browser shows "Your connection is not private"
  • Certificate expired or invalid

Diagnosis:

# Check certificate expiry
openssl s_client -connect nordabiznes.pl:443 -servername nordabiznes.pl | openssl x509 -noout -dates
# Look for notBefore and notAfter dates

Resolution:

  1. Login to NPM admin UI (http://10.22.68.250:81)
  2. Navigate to SSL Certificates → nordabiznes.pl
  3. Click "Renew" (Let's Encrypt auto-renewal)
  4. Verify renewal success

Prevention:

  • NPM has auto-renewal configured
  • Monitor certificate expiry (30 days before)

Issue 3: Slow Response Times

Symptoms:

  • Pages load slowly (>5 seconds)
  • Timeouts on some requests

Diagnosis:

# Check network latency
ping -c 10 10.22.68.249
# Expected: <2ms average

# Check database performance
sudo -u postgres psql nordabiz -c "SELECT COUNT(*) FROM pg_stat_activity;"
# Look for excessive connections

# Check Gunicorn workers
ps aux | grep gunicorn
# Should show 4 worker processes

Resolution:

  1. Restart Gunicorn to clear memory: sudo systemctl restart nordabiznes
  2. Check for slow queries: SELECT * FROM pg_stat_activity WHERE state = 'active';
  3. Review application logs: sudo journalctl -u nordabiznes -n 100

Issue 4: Cannot Connect to PostgreSQL

Symptoms:

  • Application shows "Database connection failed"
  • SQLAlchemy errors in logs

Diagnosis:

# Check if PostgreSQL is running
sudo systemctl status postgresql

# Check if listening on localhost
sudo netstat -tulpn | grep 5432
# Expected: 127.0.0.1:5432 (NOT 0.0.0.0:5432)

# Test connection
psql -h 127.0.0.1 -U nordabiz_app -d nordabiz
# Should prompt for password

Resolution:

  1. Restart PostgreSQL: sudo systemctl restart postgresql
  2. Check pg_hba.conf: sudo cat /etc/postgresql/14/main/pg_hba.conf
    • Should have: host nordabiz nordabiz_app 127.0.0.1/32 md5
  3. Check postgresql.conf: sudo cat /etc/postgresql/14/main/postgresql.conf
    • Should have: listen_addresses = 'localhost'

Issue 5: ERR_TOO_MANY_REDIRECTS (The Critical Incident)

Symptoms:

  • Browser shows "ERR_TOO_MANY_REDIRECTS"
  • Infinite redirect loop

Root Cause: NPM forwarding to port 80 instead of port 5000

Diagnosis:

# Check NPM configuration (from R11-REVPROXY-01)
docker exec -it nginx-proxy-manager cat /data/nginx/proxy_host/27.conf
# Look for: proxy_pass http://10.22.68.249:XXXX
# XXXX should be 5000, NOT 80

Resolution:

  1. Login to NPM admin UI (http://10.22.68.250:81)
  2. Navigate to Proxy Hosts → nordabiznes.pl (Host ID 27)
  3. Edit → Details tab
  4. Change "Forward Port" from 80 to 5000
  5. Save
  6. Test: curl -I https://nordabiznes.pl/health

Prevention:

  • Document critical port configuration
  • Regular configuration backups
  • Monitoring for redirect errors

Reference: INCIDENT_REPORT_20260102.md


Network Diagrams - Additional Views

Physical Network Diagram (Layer 2/3)

graph LR
    subgraph "Layer 2/3 Network Topology"
        Internet["Internet<br/>(Layer 3)"]

        FW_WAN["Fortigate WAN<br/>85.237.177.83<br/>(Layer 3)"]
        FW_LAN["Fortigate LAN<br/>10.22.68.1<br/>(Layer 3)"]

        Switch["Switch<br/>(Layer 2)<br/>10.22.68.0/24"]

        NPM_NIC["NPM NIC<br/>10.22.68.250<br/>MAC: xx:xx:xx:xx:xx:01"]

        APP_NIC["NORDABIZ-01 NIC<br/>10.22.68.249<br/>MAC: xx:xx:xx:xx:xx:02"]

        GIT_NIC["Git Server NIC<br/>10.22.68.180<br/>MAC: xx:xx:xx:xx:xx:03"]
    end

    Internet <--> FW_WAN
    FW_WAN <--> FW_LAN
    FW_LAN <--> Switch
    Switch <--> NPM_NIC
    Switch <--> APP_NIC
    Switch <--> GIT_NIC

Data Flow - Successful HTTPS Request

sequenceDiagram
    participant User as User Browser
    participant DNS as OVH DNS
    participant FW as Fortigate
    participant NPM as NPM (10.22.68.250)
    participant App as Flask (10.22.68.249)
    participant DB as PostgreSQL (127.0.0.1)

    User->>DNS: DNS query: nordabiznes.pl
    DNS-->>User: A record: 85.237.177.83

    User->>FW: HTTPS :443 (85.237.177.83)
    Note over FW: NAT: 85.237.177.83:443<br/>→ 10.22.68.250:443
    FW->>NPM: HTTPS :443 (10.22.68.250)

    Note over NPM: SSL termination<br/>Decrypt HTTPS → HTTP
    NPM->>App: HTTP :5000 (10.22.68.249)
    Note over App: Flask processes request

    App->>DB: SQL query (localhost:5432)
    DB-->>App: Result set

    App-->>NPM: HTTP response
    Note over NPM: Encrypt HTTP → HTTPS
    NPM-->>FW: HTTPS response
    Note over FW: NAT reverse
    FW-->>User: HTTPS response

Data Flow - Failed Request (Port 80 Misconfiguration)

sequenceDiagram
    participant User as User Browser
    participant FW as Fortigate
    participant NPM as NPM (10.22.68.250)
    participant Nginx as Nginx (10.22.68.249:80)

    User->>FW: HTTPS :443
    FW->>NPM: HTTPS :443
    Note over NPM: SSL termination<br/>Decrypt HTTPS → HTTP

    NPM->>Nginx: ❌ HTTP :80 (WRONG!)
    Note over Nginx: Nginx config:<br/>Redirect HTTP → HTTPS
    Nginx-->>NPM: 301 Redirect to https://nordabiznes.pl

    Note over NPM: Receives HTTPS request<br/>Terminates SSL again
    NPM->>Nginx: HTTP :80 (loop iteration 2)
    Nginx-->>NPM: 301 Redirect to https://nordabiznes.pl

    Note over NPM,Nginx: Loop continues...<br/>(20 iterations max)

    NPM-->>User: ❌ ERR_TOO_MANY_REDIRECTS

Maintenance and Change Management

Network Change Procedure

Before Making Changes:

  1. Document current configuration
  2. Create backup of NPM configuration
  3. Test in development environment (if possible)
  4. Schedule maintenance window
  5. Notify stakeholders

During Changes:

  1. Make one change at a time
  2. Test after each change
  3. Monitor logs for errors
  4. Verify health checks

After Changes:

  1. Update documentation
  2. Verify all services operational
  3. Update incident response procedures
  4. Review monitoring alerts

Configuration Backups

NPM Configuration:

# Backup NPM configuration (from R11-REVPROXY-01)
docker exec nginx-proxy-manager tar czf /tmp/npm-backup.tar.gz /data
docker cp nginx-proxy-manager:/tmp/npm-backup.tar.gz ./npm-backup-$(date +%Y%m%d).tar.gz

PostgreSQL Configuration:

# Backup PostgreSQL config (from NORDABIZ-01)
sudo tar czf postgresql-config-backup-$(date +%Y%m%d).tar.gz /etc/postgresql/14/main/

Network Configuration:

# Backup network config (from NORDABIZ-01)
sudo tar czf network-config-backup-$(date +%Y%m%d).tar.gz /etc/netplan/ /etc/systemd/network/

High-Level Architecture

Component Architecture

Network and Security

Data Flows


Glossary

Term Definition
NAT Network Address Translation - Maps public IP to internal IPs
DMZ Demilitarized Zone - Semi-trusted network zone between internet and internal network
NPM Nginx Proxy Manager - Web-based reverse proxy management
Fortigate Enterprise firewall appliance (Fortinet)
INPI InPi Infrastructure - Internal network name
SSL Termination Decrypting HTTPS traffic at proxy before forwarding to backend
Reverse Proxy Server that forwards client requests to backend servers
Layer 2 Data Link Layer - MAC addresses, switches
Layer 3 Network Layer - IP addresses, routing
Gateway Router that connects local network to external networks
Subnet Subdivision of IP network (e.g., 10.22.68.0/24)
CIDR Classless Inter-Domain Routing - IP address notation (e.g., /24)

Maintenance Notes

When to Update This Document

Update this diagram when:

  • Network topology changes (new servers, IP changes)
  • Firewall rules modified
  • NAT configuration updated
  • DNS records added/changed
  • New network zones created
  • Security boundaries redefined

What NOT to Update Here

Do not update this document for:

  • Application code changes (update Flask Components diagram instead)
  • Database schema changes (update Database Schema diagram instead)
  • Service versions (update Deployment Architecture instead)
  • API integrations (update External Integrations instead)

Review Frequency

  • Quarterly: Review and verify accuracy
  • After Major Changes: Update immediately after network/infrastructure changes
  • Incident Response: Update if incident reveals documentation gaps
  • Annual Security Audit: Comprehensive review of all network documentation

Document Status: Complete and Accurate (2026-01-10) Verified By: Auto-Claude Implementation Task Next Review Date: 2026-04-10 (Quarterly)