Skip to content

Conversation

@GhostInTheNN
Copy link

Proposed Changes

  • Added map directive to nginx.conf for universal X-Forwarded-Proto handling (works with/without upstream reverse proxy)
  • Added WebSocket support headers (Upgrade, Connection) to nginx.conf
  • Extended timeouts for WebSocket connections in nginx.conf
  • Removed --proxy-protocol True from GUNICORN_CMD_ARGS in prod.env (incompatible with standard HTTP from nginx)
  • Added pytest test suite (7 tests with value validation) to validate nginx configuration
  • Added mock backend server for header validation testing
  • Added Docker Compose test environment
  • Added GitHub Actions CI/CD workflow for automated testing
  • Added test documentation in tests/README.md

Related Issue(s)

Closes #138

Background

I encountered issues integrating wger into my home lab environment using the default docker configuration. After investigation, I identified two issues that affect deployments across various network configurations:

  1. Missing X-Forwarded-Proto fallback - The nginx configuration didn't handle scenarios without an upstream reverse proxy, causing Django CSRF validation failures
  2. Gunicorn PROXY protocol mismatch - The --proxy-protocol True flag was incompatible with nginx's standard HTTP communication

Testing

All changes have been validated locally and pass the test suite:

cd tests
docker compose -f docker-compose.test.yml up --abort-on-container-exit test
# Result: 7 passed

Tests cover:

  • Reverse proxy scenarios (HTTP/HTTPS preservation)
  • Direct connection fallback
  • WebSocket header proxying
  • Standard proxy headers (X-Real-IP, X-Forwarded-For, Host)

Deployment Scenarios Supported

✅ Behind reverse proxy (Traefik, Caddy, nginx)
✅ Direct connection
✅ Port forwarding with/without reverse proxy
✅ WebSocket connections

These changes are backwards compatible and don't break existing deployments.

GhostInTheNN and others added 12 commits October 19, 2025 16:38
…ehensive testing

This commit introduces fixes and testing infrastructure for nginx proxy configuration:

**Configuration Fixes (config/nginx.conf):**
- Universal X-Forwarded-Proto handling using nginx map directive
  - Forwards upstream value when behind reverse proxy (Traefik, Caddy, etc.)
  - Falls back to $scheme for direct connections and port forwarding
- WebSocket support with proper headers (Upgrade, Connection)
- Extended timeouts for WebSocket connections (86400s)
- HTTP/1.1 protocol version for proxy connections

**Testing Infrastructure (NEW):**
- Mock backend server (tests/mock_backend.py) - echoes headers as JSON
- Comprehensive test suite (tests/test_nginx_scenarios.sh) - 7 scenarios
  1. Reverse proxy with HTTP (X-Forwarded-Proto: http)
  2. Reverse proxy with HTTPS (X-Forwarded-Proto: https)
  3. Direct HTTP connection (fallback to $scheme)
  4. WebSocket upgrade headers
  5. X-Real-IP forwarding
  6. X-Forwarded-For forwarding
  7. Host header forwarding
- Docker Compose test environment (tests/docker-compose.test.yml)
- GitHub Actions CI/CD workflow (.github/workflows/test-nginx.yml)
  - Syntax validation (nginx -t)
  - Automated scenario testing
  - Test failure diagnostics

**Validated Deployment Scenarios:**
✅ User → HTTPS → Traefik → nginx → wger (X-Forwarded-Proto: https)
✅ User → HTTP → Traefik → nginx → wger (X-Forwarded-Proto: http)
✅ User → HTTP → nginx → wger (fallback to $scheme)
✅ User → Router → Port Forward → nginx → wger (fallback to $scheme)
✅ WebSocket connections (Upgrade/Connection headers proxied)

All tests passing locally (7/7). Fixes Django CSRF validation issues and enables
WebSocket functionality for real-time features.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…professional pytest suite

This commit fixes three critical configuration issues and adds a professional testing framework:

## Issue 1: Missing WebSocket Support (nginx.conf)
**Problem:** nginx configuration lacks WebSocket upgrade headers
**Symptom:** Web app login fails, real-time features don't work
**Fix:** Added proper WebSocket headers and timeouts

```nginx
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
```

## Issue 2: X-Forwarded-Proto Incompatibility (nginx.conf)
**Problem:** X-Forwarded-Proto handling breaks in direct connections and port forwarding
**Symptom:** Django CSRF validation failures, mixed content warnings
**Fix:** Universal solution using nginx map directive

```nginx
# Universal X-Forwarded-Proto handling for all deployment scenarios
map $http_x_forwarded_proto $final_forwarded_proto {
    default $http_x_forwarded_proto;  # Use upstream value (Traefik, Caddy, etc.)
    ''      $scheme;                    # Fall back to http/https for direct connections
}
```

**Validated Deployment Scenarios:**
✅ Reverse proxy setups (Traefik, Caddy, nginx, Apache)
✅ Direct HTTP/HTTPS connections
✅ Port forwarding (home router scenarios)
✅ No breaking changes to existing deployments

## Issue 3: PROXY Protocol Mismatch (config/prod.env)
**Problem:** Gunicorn expects PROXY protocol headers but nginx sends standard HTTP
**Symptom:** Connection failures between nginx and wger application
**Fix:** Removed incompatible `--proxy-protocol True` flag

```env
# Before (BROKEN):
GUNICORN_CMD_ARGS="... --proxy-protocol True ..."

# After (FIXED):
GUNICORN_CMD_ARGS="--workers 3 --threads 2 --worker-class gthread --timeout 240"
```

## Professional Testing Framework (NEW)

Replaced amateur bash scripts with professional pytest suite following Python/Django best practices:

**Test Suite (`tests/test_nginx_config.py`):**
- ✅ Proper assertions on header **VALUES**, not just presence
- ✅ Clear test names and failure messages
- ✅ 7 comprehensive test scenarios
- ✅ IPv4 address validation for X-Real-IP and X-Forwarded-For
- ✅ Professional pytest structure with fixtures and classes

**Test Coverage:**
1. Reverse proxy HTTP (X-Forwarded-Proto: http preservation)
2. Reverse proxy HTTPS (X-Forwarded-Proto: https preservation)
3. Direct connection (fallback to $scheme validation)
4. WebSocket upgrade headers (Upgrade + Connection)
5. X-Real-IP with IPv4 format validation
6. X-Forwarded-For with IP validation
7. Host header forwarding

**Test Infrastructure:**
- `tests/test_nginx_config.py` - pytest test suite (REPLACES weak bash script)
- `tests/requirements.txt` - Python test dependencies (pytest, requests)
- `tests/conftest.py` - pytest configuration with service readiness checks
- `tests/mock_backend.py` - HTTP server that echoes headers as JSON (KEPT)
- `tests/docker-compose.test.yml` - Updated for pytest execution
- `.github/workflows/test-nginx.yml` - CI/CD pipeline with pytest

**Local Test Results:**
```
========================= 7 passed, 1 warning in 0.03s =========================
```

All tests validate actual header values using proper assertions, not grep for strings.

## Files Changed
- `config/nginx.conf` - WebSocket support + universal X-Forwarded-Proto
- `config/prod.env` - Remove PROXY protocol flag
- `tests/test_nginx_config.py` - Professional pytest suite (NEW)
- `tests/requirements.txt` - Test dependencies (NEW)
- `tests/conftest.py` - pytest configuration (NEW)
- `tests/mock_backend.py` - Mock backend server (KEPT)
- `tests/docker-compose.test.yml` - Updated for pytest
- `.github/workflows/test-nginx.yml` - CI/CD with pytest
- ~~`tests/test_nginx_scenarios.sh`~~ - Amateur bash script (DELETED)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added tests/README.md documenting:
- Quick start guide for running tests
- Explanation of 3 deployment scenarios (reverse proxy, direct, port forwarding)
- Why each scenario matters (Django CSRF, WebSocket support)
- What each test validates
- Manual test execution steps
- Common troubleshooting

Assumes reader is not familiar with nginx proxy testing or deployment scenarios.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
**Changes:**

1. **docker-compose.test.yml** - Added `-p no:cacheprovider` to pytest command
   - Eliminates pytest cache warning on read-only filesystem
   - Cache not needed for CI/CD or one-off test runs

2. **README.md** - Clarified port forwarding scenarios
   - Scenario 1 now explicitly shows both direct and port-forwarded reverse proxy setups
   - Scenario 3 renamed to "Port Forwarding Without Reverse Proxy" for clarity
   - Added explanations of what happens in each scenario
   - Clearly distinguishes: Port Forward → Reverse Proxy (Scenario 1) vs Port Forward → nginx directly (Scenario 3)

All tests still passing (7/7) with no warnings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixes three critical issues preventing wger from working properly
in reverse proxy deployments:

1. Universal X-Forwarded-Proto handling
   - Added map directive to preserve upstream header value
   - Falls back to nginx $scheme for direct connections
   - Fixes Django CSRF errors behind HTTPS reverse proxies

2. WebSocket support
   - Added Upgrade and Connection headers
   - Set HTTP/1.1 and extended timeouts
   - Enables login and real-time features

3. Remove PROXY protocol from Gunicorn
   - Removed --proxy-protocol True from prod.env
   - nginx uses standard HTTP, not PROXY protocol format
   - Fixes Gunicorn connection failures

Added professional test suite:
- 7 pytest tests with value validation
- Docker Compose test orchestration
- GitHub Actions CI/CD workflow
- Comprehensive documentation

All tests passing. Tested in production home lab environment.
Addresses: wger-project#138
nginx uses standard HTTP, not PROXY protocol format.
Gunicorn's --proxy-protocol flag causes connection failures
when nginx sends standard HTTP headers.
Comprehensive test suite validating:
- X-Forwarded-Proto preservation from reverse proxy
- X-Forwarded-Proto fallback to $scheme for direct connections
- WebSocket header proxying
- Standard proxy headers (X-Real-IP, X-Forwarded-For, Host)

7 tests with proper value validation (not just presence checking)
Mock backend echoes all received headers as JSON for validation.
Docker Compose orchestrates nginx + mock backend + test runner.
Explains all deployment scenarios tested, how to run tests,
and what each test validates.
Validates nginx syntax and runs comprehensive test suite on:
- Push to master/main branches
- Pull requests
- Changes to nginx.conf or tests/
@rolandgeider
Copy link
Member

@GhostInTheNN I just now opened the pr, I had thought it was just a missing header or something like that, but you included a whole test suite! I'll properly look at it next ween when I'll have time

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this file used at all?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My apologies. I was off grid for a bit.

The file is not critical. It was created for different use cases:

  • pytest → CI/CD automation, PR validation, structured testing
  • bash → Quick local checks, debugging, immediate feedback

test_nginx_config.py

  • 7 pytest tests validating nginx proxy behavior
  • CI/CD integrated - runs automatically on GitHub Actions
  • Docker Compose orchestrated with mock backend
  • Proper assertions on actual header values
  • Primary validation for PR approval

test_nginx_scenarios.sh - The Quick Validator

  • 6 bash test scenarios covering same cases
  • Manual execution for rapid local testing
  • Colored output (green/red) for immediate feedback
  • Simple curl-based - no Docker Compose needed
  • Developer tool for quick verification

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants