WebSockets with OpenResty

Lua WebSocket Implementation Installation

This blog post is updated for OpenResty

I have been following OpenResty development closely for a while now, but I did never got an inspiration to really try it out, until now. Yichun Zhang (@agentzh) of OpenResty-fame announced that he just released a preliminary WebSockets support for Lua Nginx module (lua-nginx-module). I have been waiting for this to happen.

I managed to install, and test this on my Mac. Here is how I did it:

$ brew install pcre
$ wget http://openresty.org/download/ngx_openresty-
$ tar zxf ngx_openresty-
$ cd ngx_openresty-
$ ./configure \
--with-luajit \
--with-cc-opt="-I/usr/local/Cellar/pcre/8.33/include" \
$ make
$ make install

Now we should have OpenResty installed with Lua module that supports WebSockets at /usr/local/openresty.

Next we need to write the WebSockets server code (right now just a stupid echoing server). Again, edit nginx.conf, and add a new location after “location / { … }”:

location /s {
lua_socket_log_errors off;
lua_check_client_abort on;
content_by_lua '
local server = require "resty.websocket.server"
local wb, err = server:new{
timeout = 5000,
max_payload_len = 65535
if not wb then
ngx.log(ngx.ERR, "failed to new websocket: ", err)
return ngx.exit(444)
while true do
local data, typ, err = wb:recv_frame()
if wb.fatal then
ngx.log(ngx.ERR, "failed to receive frame: ", err)
return ngx.exit(444)
if not data then
local bytes, err = wb:send_ping()
if not bytes then
ngx.log(ngx.ERR, "failed to send ping: ", err)
return ngx.exit(444)
elseif typ == "close" then break
elseif typ == "ping" then
local bytes, err = wb:send_pong()
if not bytes then
ngx.log(ngx.ERR, "failed to send pong: ", err)
return ngx.exit(444)
elseif typ == "pong" then
ngx.log(ngx.INFO, "client ponged")
elseif typ == "text" then
local bytes, err = wb:send_text(data)
if not bytes then
ngx.log(ngx.ERR, "failed to send text: ", err)
return ngx.exit(444)

Looks great. Now add websockets.html to /usr/local/openresty/nginx/html directory:

var ws = null;
function connect() {
if (ws !== null) return log('already connected');
ws = new WebSocket('ws://');
ws.onopen = function () {
ws.onerror = function (error) {
ws.onmessage = function (e) {
log('recv: ' + e.data);
ws.onclose = function () {
ws = null;
return false;
function disconnect() {
if (ws === null) return log('already disconnected');
return false;
function send() {
if (ws === null) return log('please connect first');
var text = document.getElementById('text').value;
document.getElementById('text').value = "";
log('send: ' + text);
return false;
function log(text) {
var li = document.createElement('li');
return false;
<form onsubmit="return send();">
<button type="button" onclick="return connect();">
<button type="button" onclick="return disconnect();">
<input id="text" type="text">
<button type="submit">Send</button>
<ol id="log"></ol>

And now start the nginx with:

sudo /usr/local/openresty/nginx/sbin/nginx

Then open a browser that has WebSocket support enabled, and open following url:
Lua Web Sockets in Action

To guard against half-open TCP connections, it is a good idea to enable TCP keepalive in your Nginx listen configuration directive:

# so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
# for example:
listen 80 so_keepalive=2s:2s:8;

(nginx documentation about so_keepalive)

You could also do this on system level, if you wish:

$ sysctl net.inet.tcp.always_keepalive
net.inet.tcp.always_keepalive: 0
$ sudo sysctl -w net.inet.tcp.always_keepalive=1
net.inet.tcp.always_keepalive: 0 -> 1

(for Linux, see: Using TCP keepalive under Linux)