server adminstration

Installing and setting up OpenResty in Ubuntu

published on
OpenResty logo
https://openresty.org/en/

In an effort to experiment with different web servers I decided to give OpenResty a try. OpenResty enhances NGinx by including a number of NGinx modules along with other things such as Lua and a number of Lua libraries.

This server can be used just like NGinx can along with the same configuration files but can also be used as a web app server as it includes the necessary lua modules for this.

Installing OpenResty on Ubuntu

When I was looking for instructions to install OpenResty on Ubuntu I found a number of different places offering ways of installing it. However, even using them I had to tweak, adapt and update instructions in order to get a working copy of OpenResty installed.

I have therefore documented this here for anyone who is interested.

You may also already have a working NGinx configuration file and server blocks that you choose to use. If not I have put together some here. However, it is worth mentioning that you should try and work out what each part does and only include it in your version if you want or need it.

Getting started

sudo apt-get -y install wget gnupg ca-certificates
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -

If you are using an x86/x64 system you should run the following command
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list

If you are using a system with an arm architecture then run the following command instead
echo "deb http://openresty.org/package/arm64/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list

To install OpenResty
sudo apt update
sudo apt install openresty openresty-resty openresty-restydoc openresty-opm openresty-zlib openresty-pcre luarocks

OpenResty should automatically start but just in case it doesn't

sudo systemctl start openresty
sudo systemctl enable openresty

Install a firewall

sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw logging low
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

Install certbot for your free LetsEncrypt certificates

sudo apt-get install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Initial configuration

You will want to set up a new directory for the logs to be written to and a new directory for all your site configuration files.

sudo mkdir /var/log/openresty
sudo mkdir /usr/local/openresty/nginx/sites

Set up nginx.conf

Now it is time to create the nginx.conf file. I remove the existing one and replace it with one of my own.

sudo mv /usr/local/openresty/nginx/conf/nginx.{conf,original}
sudo nano /usr/local/openresty/nginx/conf/nginx.conf

My nginx.conf version looks like this

user www-data;
worker_processes auto;
pid /usr/local/openresty/nginx/logs/nginx.pid;

events {
   worker_connections  1024;
    use                 epoll; 
    epoll_events        512; use epoll;

}

http {
  server_tokens off;

  include       mime.types;
  default_type  application/octet-stream;
  charset utf-8;
  charset_types
    text/css
    text/plain
    text/vnd.wap.wml
    text/javascript
    text/markdown
    text/calendar
    text/x-component
    text/vcard
    text/cache-manifest
    text/vtt
    application/json
    application/manifest+json;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
     '$status $body_bytes_sent "$http_referer" '
     '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/openresty/access.log combined;
    error_log /var/log/openresty/error.log warn;

    client_body_buffer_size 16K;
    client_header_buffer_size 1m;
    client_max_body_size 8m;
    large_client_header_buffers 4 8k;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    gzip            on;
    gzip_vary       on;
    gzip_proxied    any;
    gzip_comp_level 5;
    gzip_buffers    16 8k;
    gzip_min_length 256;
    gzip_types
      application/atom+xml
      application/geo+json
      application/javascript
      application/x-javascript
      application/json
      application/ld+json
      application/manifest+json
      application/rdf+xml
      application/rss+xml
      application/vnd.ms-fontobject
      application/wasm
      application/x-web-app-manifest+json
      application/xhtml+xml
      application/xml
      font/eot
      font/otf
      font/ttf
      image/bmp
      image/svg+xml
      text/cache-manifest
      text/calendar
      text/css
      text/javascript
      text/markdown
      text/plain
      text/xml
      text/vcard
      text/vnd.rim.location.xloc
      text/vtt
      text/x-component
      text/x-cross-domain-policy;
    gzip_disable    "MSIE [1-6]\.";

    reset_timedout_connection on;
    keepalive_timeout 20s;
    keepalive_requests 30;
    client_header_timeout 10;
    client_body_timeout 10;
    send_timeout 10s;

  directio 4m;
    directio_alignment 512;

    open_file_cache max=1000 inactive=30s; 
    open_file_cache_valid 30s; 
    open_file_cache_min_uses 4; 
    open_file_cache_errors on; 

    # Limits
    limit_req_log_level warn;
    limit_req_zone $binary_remote_addr zone=reqlimit:10m rate=10r/m;

    limit_conn_zone $binary_remote_addr zone=connlimit:100m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

include ../sites/*;
}

You will notice the include ../sites/*; line at the end. This is how we will include any other sites you wish to serve on this machine but keep each of their configuration in seperate files.

Set up an initial server block for SSL certificate purposes

All server block configuration should be saved in /usr/local/openresty/nginx/sites/. I tend to name each file after the domain name I am using for the site. For example, if I was going to serve this site in OpenResty / NGinx I would call the configuration file philipstone.net.conf.

Add the following configuration to your file:

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /usr/local/openresty/nginx/html/[domain];

    # Add index.php if you are using PHP.
    index index.html index.htm;

    server_name [domain] www.[domain];

    # When we try to access this site...
    location / {
        try_files $uri $uri/ =404;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root /usr/local/openresty/nginx/html;
    }
}

Now you have saved your server block configuration you need to create the directory you specified in the root directive above (e.g. root /usr/local/openresty/nginx/html/example.com;) and set the necessary permissions so that the server can read your files.

sudo mkdir /usr/local/openresty/nginx/html/[domain]
sudo chown -R www-data: /usr/local/openresty/nginx/html/[domain]
sudo chmod -R 750 /usr/local/openresty/nginx/html/[domain]/

Finally, reload the server to enable your new configuration

sudo systemctl reload openresty

Getting your SSL certificate

You will want to make sure your site has a working SSL certificate. If it doesn't it could affect your site's SEO score. To get a certificate for your site type

sudo certbot certonly --webroot -w /usr/local/openresty/nginx/html/[domain] -d [domain] -d www.[domain]

Lastly, test the certificate renewal will work.

certbot renew --dry-run

Update your site server block

Now that you have a SSL certificate it is time to replace your existing server block with one that will be more useful to you. I have included my sample server block file below. Please remember to alter it to suit your own requirements.

Some things you need to make note of are

  • The listen [::]:80 (or 443) directives are only of any use to you if your server has an ipv6 address. Likewise listen 80 (or 443) directives are only of any use if you have an ipv4 address. Obviously you must have at least one of these.
  • I have used example.com and www.example.com as a server name here. This must be changed to your own domain name.
  • There is a redirect set up to always visit the https://www. version of the site. If this isn't what you want then remove the www. on the return 301 https://www.$server_name$request_uri; line.
  • The content-security-policy and similar headers will need adapting to meet your own needs.
  • Find the caching section, near the end, and update it to reflect the settings that you prefer.
  • The SSL certificate file paths will need to be updated with the ones you were given when running certbot.

server {
  listen 80;
  listen [::]:80 ipv6only=on;
  server_name example.com www.example.com;
  # Allow access to the ACME Challenge for Let's Encrypt <- <3
  location ~ /\.well-known\/acme-challenge {
    allow all;
  }
  return 301 https://www.$server_name$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ipv6only=on ssl http2;

  server_name example.com www.example.com;
  root /usr/local/openresty/nginx/html/example.com;

  # Add index.php if you are using PHP.
  index index.html index.htm;

  charset utf-8;
  source_charset utf-8;

  ssl_certificate /path/to/signed_cert_plus_intermediates;
  ssl_certificate_key /path/to/private_key;
  ssl_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
  ssl_session_tickets off;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers off;

  ssl_stapling on;
  ssl_stapling_verify on;

  # When we try to access this site...
  location / {
      try_files $uri $uri/ =404;
  }

  # Httproxy vulnerability
  proxy_set_header Proxy "";

  # Request headers for overwriting
  proxy_set_header X-Original-URL "";
  proxy_set_header X-Rewrite-URL "";
  proxy_set_header X-Rewrite-URL "";
  proxy_set_header X-Host "";
  proxy_set_header X-Forwarded-Server "";
  proxy_set_header X-HTTP-Host-Override "";
  proxy_set_header Forwarded "";

  # Prevent Information leaks
  proxy_hide_header X-Powered-By;
  proxy_hide_header Server;
  proxy_hide_header X-AspNetMvc-Version;
  proxy_hide_header X-AspNet-Version;

  # http://blog.portswigger.net/2017/07/cracking-lens-targeting-https-hidden.html
  proxy_set_header clientIPAddress "";
  proxy_set_header x-forwarded-for "";
  proxy_set_header client-ip "";
  proxy_set_header forwarded "";
  proxy_set_header from  "";
  proxy_set_header referer "";
  proxy_set_header x-client-ip "";
  proxy_set_header x-originating-ip "";
  proxy_set_header x-wap-profile "";

  # http security headers
  add_header X-Content-Type-Options nosniff;
  add_header X-Frame-Options DENY;
  add_header Pragma no-cache;
  add_header Cache-Control no-store;
  add_header X-XSS-Protection "1; mode=block";
  add_header Referrer-Policy origin-when-cross-origin;
  add_header X-Permitted-Cross-Domain-Policies none;

  # Add Security cookie flags 
  proxy_cookie_path ~(.*) "$1; SameSite=strict; secure; httponly";

  # nonce!!, upgrade-insecure-requests!!
  add_header Content-Security-Policy "upgrade-insecure-requests; default-src 'self'; base-uri 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-JjECqn6A' http: https:; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; media-src 'none'; frame-src 'self'; font-src 'self'; connect-src 'self' wss:;";

  #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;" always;

  # add_header Allow "GET, POST, HEAD";
  ## Only allow these request methods ##
  ## Do not accept DELETE, SEARCH and other methods ##
  if ($request_method !~ ^(GET|HEAD|POST)$ ) {
      return 444;
  }

  # Allow access to the ACME Challenge for Let's Encrypt <- <3
  location ~ /\.well-known\/acme-challenge {
    allow all;
  }

  # Deny all attempts to access hidden files
  # such as .htaccess, .htpasswd, .DS_Store (Mac), .git, .etc...
  location ~ /\. {
      deny all;
  }

  # caching - please check values
  location ~* .(ttf|mp3|mp4|webm|ogg|jpg|jpeg|png|gif|ico)$ {
    expires 1M;
  }
  location ~* .(css|js)$ {
    expires 1w;
  }

  error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 420 422 423 424 426 428 429 431 444 449 450 451 500 501 502 503 504 505 506 507 508 509 510 511 /error.html;
  location /error.html {
    internal;
  }
    limit_conn connlimit 1000; # Simultaneous Connections
}

and finally restart the server to see your changes take effect

sudo systemctl reload openresty

Finally

Everything should now be installed and working perfectly. Your first site should also be online (which you can check by visiting your domain in a browser). If you can't access your site for some reason then make sure the permissions are set correctly by running the following command again

sudo chmod -R 750 /usr/local/openresty/nginx/html/[domain]/

In you would like to learn more about OpenResty, including looking through some of their video tutorials, visit https://openresty.org/en/.