Saltar a contenido

Configuración de Nginx

omegaUp utiliza Nginx como servidor web y proxy inverso. Esta guía cubre la configuración para entornos de desarrollo y producción.

Arquitectura

flowchart LR
    Client[Browser] --> Nginx

    subgraph Nginx
        Static[Static Files]
        PHP[PHP Proxy]
        WS[WebSocket Proxy]
        Grader[Grader Proxy]
    end

    PHP --> PHPFPM[PHP-FPM :9000]
    WS --> Broadcaster[Broadcaster :22291]
    Grader --> GraderSvc[Grader :36663]

Configuración de desarrollo

La configuración de desarrollo de Nginx se encuentra en stuff/docker/etc/nginx/nginx.conf:

daemon off;
pid /tmp/nginx.pid;
worker_processes 1;

error_log /dev/stderr error;

events {
    worker_connections 1024;
}

http {
    # Temp directories for non-root operation
    client_body_temp_path /tmp/client_body;
    fastcgi_temp_path /tmp/fastcgi_temp;
    proxy_temp_path /tmp/proxy_temp;

    access_log /dev/stderr;

    # Buffer sizes for proxying
    proxy_busy_buffers_size 512k;
    proxy_buffers 4 512k;
    proxy_buffer_size 256k;

    include /etc/nginx/mime.types;

    # PHP-FPM upstream
    upstream php {
        server 127.0.0.1:9000;
    }

    server {
        listen 8001 default_server;
        listen [::]:8001 default_server ipv6only=on;

        root /opt/omegaup/frontend/www;
        index index.php index.html;

        # Default location
        location / {
            index index.php index.html;
        }

        # PHP handling
        location ~* "\.php(/|$)" {
            fastcgi_index index.php;
            fastcgi_keep_conn on;

            # FastCGI buffers
            fastcgi_buffer_size 64k;
            fastcgi_buffers 16 32k;
            fastcgi_busy_buffers_size 64k;

            # FastCGI parameters
            fastcgi_param QUERY_STRING $query_string;
            fastcgi_param REQUEST_METHOD $request_method;
            fastcgi_param CONTENT_TYPE $content_type;
            fastcgi_param CONTENT_LENGTH $content_length;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param SCRIPT_NAME $fastcgi_script_name;
            fastcgi_param REQUEST_URI $request_uri;
            fastcgi_param DOCUMENT_URI $document_uri;
            fastcgi_param DOCUMENT_ROOT $document_root;
            fastcgi_param SERVER_PROTOCOL $server_protocol;
            fastcgi_param GATEWAY_INTERFACE CGI/1.1;
            fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
            fastcgi_param REMOTE_ADDR $remote_addr;
            fastcgi_param REMOTE_PORT $remote_port;
            fastcgi_param SERVER_ADDR $server_addr;
            fastcgi_param SERVER_PORT $server_port;
            fastcgi_param SERVER_NAME $server_name;
            fastcgi_param HTTPS $https;
            fastcgi_param REDIRECT_STATUS 200;

            fastcgi_pass 127.0.0.1:9000;
        }

        # WebSocket endpoint for real-time events
        location ^~ /events/ {
            rewrite ^/events/(.*) /$1 break;
            proxy_pass http://broadcaster:22291;
            proxy_read_timeout 90;
            proxy_connect_timeout 90;
            proxy_redirect off;

            # WebSocket upgrade headers
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_http_version 1.1;
        }

        # Grader web interface
        location /grader/ {
            try_files $uri $uri/ @grader;
        }

        location @grader {
            rewrite ^/grader/(.*) /$1 break;
            proxy_pass http://grader:36663;
        }

        # URL rewrites
        include /opt/omegaup/frontend/server/nginx.rewrites;
    }
}

Configuración de producción

Configuración HTTPS

server {
    listen 80;
    server_name omegaup.com www.omegaup.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name omegaup.com www.omegaup.com;

    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/omegaup.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/omegaup.com/privkey.pem;

    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    root /opt/omegaup/frontend/www;
    index index.php index.html;

    # ... rest of configuration
}

Limitación de velocidad

http {
    # Define rate limit zones
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=submit:10m rate=2r/s;

    server {
        # API rate limiting
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            # ... PHP handling
        }

        # Login rate limiting (stricter)
        location /api/Session/login/ {
            limit_req zone=login burst=5;
            # ... PHP handling
        }

        # Submission rate limiting
        location /api/Run/create/ {
            limit_req zone=submit burst=10;
            # ... PHP handling
        }
    }
}

Almacenamiento en caché de activos estáticos

server {
    # Static assets with long cache
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # HTML with short cache
    location ~* \.html$ {
        expires 1h;
        add_header Cache-Control "public, must-revalidate";
    }
}

Compresión Gzip

http {
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        application/json
        application/javascript
        application/xml
        application/xml+rss
        image/svg+xml;
    gzip_min_length 256;
}

Reescrituras de URL

El archivo nginx.rewrites maneja URL limpias:

# API endpoints
rewrite ^/api/(.*)$ /api/ApiEntryPoint.php last;

# Contest arena
rewrite ^/arena/([^/]+)/$ /arena/contest.php?contest_alias=$1 last;
rewrite ^/arena/([^/]+)/scoreboard/([^/]+)/$ /arena/scoreboard.php?contest_alias=$1&scoreboard_token=$2 last;

# Problem pages
rewrite ^/problem/([^/]+)/$ /problem/problem.php?problem_alias=$1 last;
rewrite ^/problem/([^/]+)/edit/$ /problem/edit.php?problem_alias=$1 last;

# User profiles
rewrite ^/profile/([^/]+)/$ /profile/profile.php?username=$1 last;

# Course pages
rewrite ^/course/([^/]+)/$ /course/course.php?course_alias=$1 last;

# Group pages
rewrite ^/group/([^/]+)/$ /group/group.php?group_alias=$1 last;

Equilibrio de carga (múltiples servidores)

upstream php_backend {
    least_conn;
    server php1:9000 weight=5;
    server php2:9000 weight=5;
    server php3:9000 backup;
}

upstream grader_backend {
    server grader1:21680;
    server grader2:21680;
}

Punto final de verificación de estado

server {
    location /health/ {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }

    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

Registro

Formato de registro de acceso

http {
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time';

    log_format json escape=json '{'
        '"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"request":"$request",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_response_time":"$upstream_response_time"'
    '}';

    access_log /var/log/nginx/access.log json;
}

Solución de problemas

Problemas comunes

502 Puerta de enlace incorrecta:

# Check PHP-FPM is running
docker-compose logs frontend | grep php-fpm
Tiempo de espera de puerta de enlace 504:
# Increase timeouts
fastcgi_read_timeout 300;
proxy_read_timeout 300;
Error de conexión a WebSocket:
# Ensure upgrade headers are set
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Configuración de prueba

# Test configuration syntax
nginx -t

# Reload configuration
nginx -s reload

# View error logs
tail -f /var/log/nginx/error.log

Documentación relacionada