Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Configuring Nginx for WebSocket and HTTP Coexistence in Docker Environments

Tech 1

Initial Configuration Challenges

A recent project required serving both HTTP and WebSocket traffic through a single nginx configuration file. The initial setup was as follows:

upstream backend_service {
    server 10.6.14.200:8000 max_fails=0;
}

server {
    listen 80;
    
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 9;
    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";

    root /usr/share/nginx/html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /websocket {
        proxy_pass http://backend_service;

        proxy_http_version 1.1;
        proxy_read_timeout 360s;   
        proxy_redirect off;   
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

While HTTP requests functioned correctly, WebSocket connections consistently failed to establish.

Enhanced Configuration Supporting Both Protocols

According to nginx documentation on WebSocket handling, custom variables can be defined using the map directive. The improved configuration below supports both WebSocket and HTTP requests:

upstream backend_service {
    server 10.6.14.200:8000 max_fails=0;
}

map $http_upgrade $connection_protocol {
    default     keep-alive;
    'websocket' upgrade;
}

server {
    listen 80;
    
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 9;
    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";

    root /usr/share/nginx/html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /websocket {
        proxy_pass http://backend_service;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_protocol;
    }
}

Dynamic Backend Configuration with Environment Variables

Nginx does not native support environment variables in configuration files. However, envsubst provides a viable workaround for dynamic configuration generation:

# nginx.conf.template
upstream backend_service {
    server ${BACKEND_HOST}:${BACKEND_PORT} max_fails=0;
}

map $http_upgrade $connection_protocol {
    default     keep-alive;
    'websocket' upgrade;
}

server {
    listen 80;
    
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 9;
    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";

    root /usr/share/nginx/html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /websocket {
        proxy_pass http://backend_service;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_protocol;
    }
}

Project directory structure:

├── dist
├── docker-entrypoint.sh
├── Dockerfile
└── nginx.conf.template

Antrypoint script for dynamic configuration:

#!/bin/bash
set -eu

envsubst '${BACKEND_HOST} ${BACKEND_PORT}' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf

exec nginx -g "daemon off;"

Dockerfile definition:

FROM nginx:alpine

COPY nginx.conf.template /etc/nginx/conf.d/nginx.conf.template
COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY dist /usr/share/nginx/html

RUN chmod +x /docker-entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/docker-entrypoint.sh"]

Build command:

docker build -t web-app:latest .

Run commend with environment variables:

docker run -d \
  --restart=always \
  -p 10086:80 \
  -e BACKEND_HOST=10.6.14.200 \
  -e BACKEND_PORT=8000 \
  --name web-container \
  web-app:latest

This approach works well for single-container deployments. For orchestrated environments like Docker Compose or Kubernetes, internal service discovery mechanisms provide more streamlined connectivity without requiring these manual steps.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.