# 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 Partner 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:**
- [Deployment Architecture](03-deployment-architecture.md) - Infrastructure and service deployment view
- [Container Diagram](02-container-diagram.md) - Application container architecture
- [HTTP Request Flow](flows/06-http-request-flow.md) - Complete request path through network layers
---
## Network Topology Diagram
```mermaid
graph TB
%% External network
subgraph "Public Internet (Untrusted)"
Users["👥 External Users
Website Visitors"]
DNS_OVH["🌐 OVH DNS
nordabiznes.pl
→ 85.237.177.83"]
end
%% Perimeter security
subgraph "Network Perimeter"
Fortigate["🛡️ FORTIGATE FIREWALL
WAN: 85.237.177.83
LAN: 10.22.68.1
NAT Rules:
• 85.237.177.83:443 → 10.22.68.250:443
• 85.237.177.83:80 → 10.22.68.250:80
Firewall Policy:
• Allow HTTPS (443) from ANY
• Allow HTTP (80) from ANY
• Allow SSH (22) from ADMIN_NET
• 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
IP: 10.22.68.250
VM ID: 119
Hostname: r11-revproxy-01
Services:
• NPM (Docker) :443, :80, :81
• SSH :22
Gateway: 10.22.68.1
DNS: 10.22.68.1"]
end
%% Application Zone
subgraph "Application Zone (Trusted)"
App_Server["🖥️ NORDABIZ-01
IP: 10.22.68.249
VM ID: 249
Hostname: nordabiz-01
DNS: nordabiznes.inpi.local
Services:
• Flask/Gunicorn :5000
• PostgreSQL :5432 (localhost)
• Nginx :80, :443 (system)
• SSH :22
Gateway: 10.22.68.1
DNS: 10.22.68.1"]
end
%% Internal Services Zone
subgraph "Internal Services Zone (Trusted)"
Git_Server["🖥️ r11-git-inpi
IP: 10.22.68.180
Hostname: r11-git-inpi
Services:
• Gitea :3000 (HTTPS)
• SSH :22
Gateway: 10.22.68.1"]
end
%% Internal DNS
Internal_DNS["🔍 Internal DNS
Zone: inpi.local
Records:
• nordabiznes.inpi.local → 10.22.68.249
• git.inpi.local → 10.22.68.180"]
end
%% External services
subgraph "External APIs (Internet)"
API_Google["☁️ Google Cloud APIs
• Gemini AI API
• PageSpeed Insights API
• Places API
Auth: API Keys
HTTPS only"]
API_MS["☁️ Microsoft Graph API
• Email sending
Auth: OAuth 2.0
HTTPS only"]
API_Brave["☁️ Brave Search API
• News search
Auth: API Key
HTTPS only"]
API_KRS["🏛️ KRS Open API
• Company registry
Auth: Public
HTTPS only"]
Web_Scrapers["🌐 Web Scraping
• ALEO.com (NIP)
• rejestr.io (Connections)
HTTPS only"]
end
%% Network flows - User traffic
Users -->|"DNS Query
nordabiznes.pl"| DNS_OVH
DNS_OVH -->|"DNS Response
85.237.177.83"| Users
Users -->|"HTTPS :443
HTTP :80"| Fortigate
%% Firewall to DMZ
Fortigate -->|"NAT + Route
:443 → 10.22.68.250:443
:80 → 10.22.68.250:80"| NPM_Server
%% DMZ to Application Zone
NPM_Server ==>|"⚠️ CRITICAL
HTTP :5000
(NOT port 80!)"| App_Server
NPM_Server -.->|"Internal DNS Query
nordabiznes.inpi.local"| Internal_DNS
%% Application to External APIs
App_Server -->|"HTTPS
API Requests"| API_Google
App_Server -->|"HTTPS
OAuth 2.0"| API_MS
App_Server -->|"HTTPS
API Requests"| API_Brave
App_Server -->|"HTTPS
API Requests"| API_KRS
App_Server -->|"HTTPS
Web Scraping"| Web_Scrapers
%% Git deployment
App_Server -.->|"git pull
HTTPS :3000
Deployment only"| Git_Server
%% Admin SSH access
Fortigate -.->|"SSH :22
Admin only"| NPM_Server
Fortigate -.->|"SSH :22
Admin only"| App_Server
Fortigate -.->|"SSH :22
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:**
```yaml
# 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:**
```yaml
# ❌ WRONG - Causes infinite redirect loop
forward_port: 80 # This forwards to nginx on NORDABIZ-01, which redirects to HTTPS
```
**Correct Configuration:**
```yaml
# ✅ CORRECT - Forwards directly to Flask/Gunicorn
forward_port: 5000 # Direct connection to application server
```
**Verification:**
```bash
# 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](../../docs/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:**
- Repository: maciejpi/nordabiz
- URL: https://10.22.68.180:3000/maciejpi/nordabiz.git
- SSL verification: disabled for internal self-signed cert
- Authentication: username + password (over HTTPS)
---
### 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:**
```bash
# 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:**
```bash
# /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)
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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](../../docs/INCIDENT_REPORT_20260102.md)
---
## Network Diagrams - Additional Views
### Physical Network Diagram (Layer 2/3)
```mermaid
graph LR
subgraph "Layer 2/3 Network Topology"
Internet["Internet
(Layer 3)"]
FW_WAN["Fortigate WAN
85.237.177.83
(Layer 3)"]
FW_LAN["Fortigate LAN
10.22.68.1
(Layer 3)"]
Switch["Switch
(Layer 2)
10.22.68.0/24"]
NPM_NIC["NPM NIC
10.22.68.250
MAC: xx:xx:xx:xx:xx:01"]
APP_NIC["NORDABIZ-01 NIC
10.22.68.249
MAC: xx:xx:xx:xx:xx:02"]
GIT_NIC["Git Server NIC
10.22.68.180
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
```mermaid
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
→ 10.22.68.250:443
FW->>NPM: HTTPS :443 (10.22.68.250)
Note over NPM: SSL termination
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)
```mermaid
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
Decrypt HTTPS → HTTP
NPM->>Nginx: ❌ HTTP :80 (WRONG!)
Note over Nginx: Nginx config:
Redirect HTTP → HTTPS
Nginx-->>NPM: 301 Redirect to https://nordabiznes.pl
Note over NPM: Receives HTTPS request
Terminates SSL again
NPM->>Nginx: HTTP :80 (loop iteration 2)
Nginx-->>NPM: 301 Redirect to https://nordabiznes.pl
Note over NPM,Nginx: Loop continues...
(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:**
```bash
# 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:**
```bash
# Backup PostgreSQL config (from NORDABIZ-01)
sudo tar czf postgresql-config-backup-$(date +%Y%m%d).tar.gz /etc/postgresql/14/main/
```
**Network Configuration:**
```bash
# Backup network config (from NORDABIZ-01)
sudo tar czf network-config-backup-$(date +%Y%m%d).tar.gz /etc/netplan/ /etc/systemd/network/
```
---
## Related Documentation
### High-Level Architecture
- [System Context Diagram](01-system-context.md) - External actors and system boundary
- [Container Diagram](02-container-diagram.md) - Major containers and their interactions
- [Deployment Architecture](03-deployment-architecture.md) - Infrastructure deployment view
### Component Architecture
- [Flask Components](04-flask-components.md) - Application component architecture
- [Database Schema](05-database-schema.md) - Database entity relationships
- [External Integrations](06-external-integrations.md) - External API integrations
### Network and Security
- **Current Document:** Network Topology Diagram
- [Critical Configurations](08-critical-configurations.md) - NPM, SSL, port mappings (to be created)
- [Security Architecture](09-security-architecture.md) - Security boundaries and auth flows (to be created)
### Data Flows
- [Authentication Flow](flows/01-authentication-flow.md) - User login and session management
- [Search Flow](flows/02-search-flow.md) - Company search implementation
- [AI Chat Flow](flows/03-ai-chat-flow.md) - AI chat context and API integration
- [SEO Audit Flow](flows/04-seo-audit-flow.md) - SEO audit process
- [News Monitoring Flow](flows/05-news-monitoring-flow.md) - News monitoring workflow
- [HTTP Request Flow](flows/06-http-request-flow.md) - Complete HTTP request path
---
## 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)