This episode explains WebSocket proxying with upgrade headers, realtime SSE configuration with buffering disabled, and gRPC service proxying with the grpc_pass directive.

Modern applications don't just send and receive data. Chat, realtime notifications, and microservices communicate with specialized protocols: WebSocket, Server-Sent Events, and gRPC. This Episode 13 covers how NGINX proxies all three correctly.
All three protocols fail completely if treated like ordinary HTTP. WebSocket needs special upgrade headers, SSE is disrupted by buffering, and gRPC requires a dedicated listener. After this episode, you'll understand how each one must be handled differently.
WebSocket starts as HTTP, then the connection gets upgraded to bidirectional. The upgrade process happens through the Upgrade and Connection headers. NGINX must forward both headers so the backend and client know they're speaking WebSocket:
location /ws/ {
proxy_pass http://backend_realtime;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}Without proxy_set_header Upgrade $http_upgrade; and Connection "upgrade", the connection won't be upgraded and WebSocket fails. The other keys: proxy_http_version 1.1; because HTTP/1.0 doesn't support upgrade headers, and extended timeouts because WebSocket connections can stay open for a long time.
SSE is a one-way data stream from the server to the client: the server sends events continuously over a single HTTP connection. The problem is that NGINX accumulates responses in a buffer before sending by default. For SSE, this buffering holds back events and destroys its realtime nature:
location /events/ {
proxy_pass http://backend_realtime;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_read_timeout 3600s;
}proxy_buffering off; — responses are forwarded to the client immediately, events aren't held back.proxy_set_header Connection ""; — drops the connection header, preventing NGINX from closing idle connections.proxy_read_timeout 3600s; — SSE connections can last a very long time.With this configuration, every event the backend sends reaches the browser without buffering delay.
gRPC is a high-performance RPC built on HTTP/2. Because it runs over HTTP/2, it can't be proxied through an ordinary HTTP/1.1 listener. Modern NGINX provides a dedicated directive:
server {
listen 50051 http2;
listen 50051 ssl http2;
grpc_pass grpcs://backend_grpc_service;
}
upstream backend_grpc_service {
server 10.0.2.21:50051;
server 10.0.2.22:50051;
}Notice the listen 50051 http2; and listen 50051 ssl http2; directives for encrypted gRPC traffic, and grpc_pass grpcs://backend_grpc_service; as the target. grpc_pass resembles proxy_pass, but understands the gRPC protocol: streaming, metadata, and error codes are forwarded natively.
Many applications use gRPC internally and REST for the public. Both can coexist:
server {
listen 443 ssl http2;
server_name api.example.com;
location / {
grpc_pass grpcs://backend_grpc_service;
}
location /rest/ {
proxy_pass http://backend_rest;
}
}One HTTP/2 listener on port 443 serves both gRPC and REST at the same time; routing is decided by the path.
Episode 13 completed the realtime protocols: you can proxy WebSocket with upgrade headers, serve SSE without buffering, and forward gRPC with grpc_pass over HTTP/2.
Key takeaways:
Upgrade and Connection "upgrade" plus HTTP/1.1.proxy_buffering off makes SSE realtime without delays.proxy_set_header Connection "" prevents SSE connections from being closed early.grpc_pass directive.In the next episode we'll discuss reusable, modular, and maintainable configuration architecture — the sites-available and sites-enabled convention, an includes directory for reusable snippets, and environment variable injection with envsubst in Docker.