Install nginx naxsi mod_pagespeed and spdy on Debian/Ubuntu
5 March, 2021 by
Install nginx naxsi mod_pagespeed and spdy on Debian/Ubuntu

This is guide to installing and configuring Nginx to serve static files. Which is to say, this is how to install and set up Nginx to serve static files whether those files are simply stylesheets, images and JavaScript or full static sites like this one. What follows is the best practices of Nginx
Nginx beats Apache for static content
Whether you’re just building a quick live prototype or running an actual live website of static files, you’ll need a good server. So why not use Apache? Simply put, Apache is overkill.

Unlike Apache, which is a jack-of-all-trades server, Nginx was really designed to do just a few things well, one of which is to offer a simple, fast, lightweight server for static files. And Nginx is really, really good at serving static files. In fact, in my experience Nginx with PageSpeed, gzip, far future expires headers and a couple other extras I’ll mention is faster than serving static files from Amazon S33 (potentially even faster in the future if Verizon and its ilk really do start throttling cloud-based services).

Nginx is different from Apache
In its quest to be lightweight and fast, Nginx takes a different approach to modules. In Apache you can dynamically load various features using modules. You just add something like LoadModule alias_module modules/ to your Apache config files and just like that Apache loads the alias module.

Unlike Apache, Nginx can not dynamically load modules. Nginx has available what it has available when you install it.

That means if you really want to customize and tweak it, it’s best to install Nginx from source. You don’t have to install it from source. But if you really want a screaming fast server, I suggest compiling Nginx yourself, enabling and disabling exactly the modules you need. Installing Nginx from source allows you to add some third-party tools, most notably Google’s PageSpeed module, which has some fantastic tools for speeding up your site.

Luckily, installing Nginx from source isn’t too difficult. Even if you’ve never compiled any software from source, you can install Nginx. The remainder of this post will show you exactly how.

Nginx setup for static sites

Before we start installing, let’s go over the things we’ll be using to build a fast, lightweight server with Nginx.

SPDY – Nginx offers “experimental support for SPDY”, but it’s not enabled by default. We’re just going to enable it when we install Nginx. In my testing SPDY support has worked without a hitch, experimental or otherwise.

Google Page Speed – Part of Google’s effort to make the web faster, the Page Speed Nginx module “automatically applies web performance best practices to pages and associated assets”.

Headers More – This isn’t really necessary from a speed standpoint, but I often like to set custom headers and hide some headers (like which version of Nginx your server is running). Headers More makes that very easy.

Naxsi – Naxsi is a “Web Application Firewall module for Nginx”. It’s not really all that important for a server limited to static files, but it adds an extra layer of security should you decided to use Nginx as a proxy server down the road.

So we’re going to install Nginx with SPDY support and three third-party modules.

Okay, here’s the step-by-step process to installing Nginx on a Debian 7 (or Ubuntu) server.

The first step is to make sure you’re installing the latest release of Nginx. To do that check the Nginx download page for the latest version of Nginx (at the time of writing that’s 1.6.0).

Okay, SSH into your server and let’s get started.

While these instructions will work on just about any server, the one thing that will be different is how you install the various prerequisites needed to compile Nginx.

On a Debian/Ubuntu server you’d do this:

$ sudo apt-get -y install build-essential zlib1g-dev libpcre3 libpcre3-dev libbz2-dev libssl-dev tar unzip

If you’re using RHEL/Cent/Fedora you’ll want these packages:

$ sudo yum install gcc-c++ pcre-dev pcre-devel zlib-devel make

After you have the prerequisites installed it’s time to grab the latest version of Google’s Pagespeed module. Google’s Nginx PageSpeed installation instructions are pretty good, so I’ll reproduce them here with just one key change, I’m going to use the master archive rather than a numbered release.

First grab the latest version of PageSpeed:

$ cd /usr/local/src
$ wget
$ unzip

Now, before we compile pagespeed we need to grab psol, which PageSpeed needs to function properly. So, let’s cd into thengx_pagespeed-master folder and grab psol:

$ cd ngx_pagespeed-master 
$ grep psol /usr/local/src/ngx_pagespeed-master/ 
$ wget 
$ tar -xvzf 
$ cd ../

Alright, so the ngx_pagespeed module is all setup and ready to install. All we have to do at this point is tell Nginx where to find it.

Now let’s grab the Headers More and Naxsi modules as well. Again, check the Headers More and Naxsi pages to see what the latest stable version is and adjust the version numbers in the following accordingly.

$ wget 
$ unzip 
$ wget 
$ unzip

Now we have all three third-party modules ready to go, we just need to grab a copy of Nginx itself:

$ wget  
$ tar -xvzf nginx-1.6.0.tar.gz

Then we cd into the Nginx folder and compile. So, first:

$ cd nginx-1.6.0

So now we’re inside the Nginx folder, let’s configure our installation. We’ll add in all our extras and turn off a few things we don’t need. Or at least they’re things I don’t need, obviously if you need the mail modules, then delete those lines. If you don’t need SSL, you might want to skip that as well. Here’s the config setting I use (Note: all paths are for Debian servers, you’ll have to adjust the various paths accordingly for RHEL/Cent/Fedora/ servers):

$sudo ./configure --add-module=../naxsi-master/naxsi_src --prefix=/usr/local/nginx --pid-path=/var/run/ --lock-path=/var/lock/nginx.lock --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --user=www-data --group=www-data --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_stub_status_module --with-http_ssl_module --with-http_spdy_module --with-http_gzip_static_module --with-file-aio --add-module=../ngx_pagespeed-master

There are a few things worth nothing here. First off make sure that Naxsi is first. Here’s what the Naxsi wiki page has to say on that score: “Nginx will decide the order of modules according the order of the module’s directive in Nginx’s ./configure. So, no matter what (except if you really know what you are doing) put Naxsi first in your ./configure. If you don’t do so, you might run into various problems, from random/unpredictable behaviors to non-effective WAF.” The last thing you want is to think you have a web application firewall running when in fact you don’t, so stick with Naxsi first.

There are a couple other things you might want to add to this configuration. If you’re going to be serving large files, larger than your average 1.5MB HTML page, consider adding the line: –with-file-aio \, which is apparently faster than the stock sendfile option. See here for more details. There are quite a few other modules available. A full list of the default modules can be found on the Nginx site. Read through that and if there’s something else you need, just add it to the list.

Okay, we’ve told Nginx what to do, now let’s actually install it:

$ sudo make
$ sudo make install

Once make install finishes doing its thing you’ll have Nginx all set up.

Congrats! You made it.

The next step is to add Nginx to the list of things your server starts up automatically whenever it reboots. Since we installed Nginx from scratch we need to tell the underlying system what we did.

Make it Autostart

Create file /etc/init.d/nginx with following content


# Provides:           nginx
# Required-Start:     $local_fs $remote_fs $network $syslog $named
# Required-Stop:      $local_fs $remote_fs $network $syslog $named
# Default-Start:      2 3 4 5
# Default-Stop:       0 1 6
# Short-Description:  nginx LSB init script
# Description:        nginx Linux Standards Base compliant init script.

# -----------------------------------------------------------------------------
# This is free and unencumbered software released into the public domain.
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# For more information, please refer to <>
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# nginx Linux Standards Base compliant init script.
# IDEAS BY:   Karl Blessing <>
# AUTHOR:     Richard Fussenegger <>
# COPYRIGHT:  Copyright (c) 2013 Richard Fussenegger
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
#                                                                      Includes
# -----------------------------------------------------------------------------

# Load the LSB log_* functions.
. /lib/lsb/init-functions

# -----------------------------------------------------------------------------
#                                                                     Variables
# -----------------------------------------------------------------------------

# The name of the service (must be the first variable).

# Absolute path to the executable.

# Arguments that should be passed to the executable.

# Absolute path to the PID file.

# -----------------------------------------------------------------------------
#                                                                     Bootstrap
# -----------------------------------------------------------------------------

# Check return status of EVERY command
set -e

# Check if ${NAME} is a file and executable, if not assume it's not installed.
if [ ! -x ${DAEMON} ]; then
  log_failure_msg ${NAME} "not installed"
  exit 1

# This script is only accessible for root (sudo).
if [ $(id -u) != 0 ]; then
  log_failure_msg "super user only!"
  exit 1

# Always check if service is already running.
RUNNING=$(start-stop-daemon --start --quiet --pidfile ${PIDFILE} --exec ${DAEMON} --test && echo "false" || echo "true")

# -----------------------------------------------------------------------------
#                                                                     Functions
# -----------------------------------------------------------------------------

# Reloads the service.
#   0 - successfully reloaded
#   1 - reloading failed
reload_service() {
  start-stop-daemon --stop --signal HUP --quiet --pidfile ${PIDFILE} --exec ${DAEMON}

# Starts the service.
#   0 - successfully started
#   1 - starting failed
start_service() {
  start-stop-daemon --start --quiet --pidfile ${PIDFILE} --exec ${DAEMON} -- ${DAEMON_ARGS}

# Stops the service.
#   0 - successfully stopped
#   1 - stopping failed
stop_service() {
  start-stop-daemon --stop --quiet --pidfile ${PIDFILE} --name ${NAME}

# -----------------------------------------------------------------------------
#                                                                  Handle Input
# -----------------------------------------------------------------------------

case ${1} in

    if [ ${RUNNING} = "false" ]; then
      log_failure_msg ${NAME} "not running"
      log_daemon_msg ${NAME} "reloading configuration"
      reload_service || log_end_msg 1
      log_end_msg 0

    if [ ${RUNNING} = "false" ]; then
      log_failure_msg ${NAME} "not running"
      log_daemon_msg ${NAME} "restarting"
      stop_service || log_end_msg 1
      sleep 0.1
      start_service || log_end_msg 1
      log_end_msg 0

    if [ ${RUNNING} = "true" ]; then
      log_success_msg ${NAME} "already started"
      log_daemon_msg ${NAME} "starting"
      start_service || log_end_msg 1
      log_end_msg 0

    status_of_proc ${DAEMON} ${NAME} && exit 0 || exit ${?}

    if [ ${RUNNING} = "false" ]; then
      log_success_msg ${NAME} "already stopped"
      log_daemon_msg ${NAME} "stopping"
      stop_service && log_end_msg 0 || log_end_msg 1

    echo "Usage: ${NAME} {force-reload|reload|restart|start|status|stop}" >&2
    exit 1


exit 0

Make it executable:

$ sudo chmod +x /etc/init.d/nginx 

# then just:

$ sudo service nginx start #also restart, reload, stop etc

I suggest taking the last bit and turning it into an alias in your bashrcor zshrc file so that you can quickly restart/reload the server when you need it. Here’s what I use:
alias xrestart=”sudo service nginx restart”
alias xreload=”sudo service nginx reload”

Okay so we now have the initialization script all set up, now let’s make Nginx start up on reboot. In theory this should do it:

$ update-rc.d -f nginx defaults

So there we have it, everything you need to get Nginx installed withSPDY, PageSpeed, Headers More and Naxsi. A blazing fast server for static files.

After that it’s just a matter of configuring Nginx, which is entirely dependent on how you’re using it. For static setups like this my configuration is pretty minimal.

Before we get to that though, there’s the first thing I do: edit/etc/nginx/nginx.conf down to something pretty simple. This is the root config so I keep it limited to just a http block that turns on a few things I want globally and an include statement that loads site-specific config files. Something a bit like this:

user www-data;
worker_processes  1;
worker_priority   15;
worker_rlimit_nofile 100000;
events {
    ### determines how much clients will be served per worker
    ### max clients = worker_connections * worker_processes
    worker_connections  1024;
    ### optmized to serve many clients with each thread, essential for linux
    use epoll;
    ### accept as many connections as possible, may flood worker connections if set too low
    multi_accept on;
http {
### Size limits
# client_body_buffer_size       8k;
# client_header_buffer_size     1k;
# client_max_body_size          1m;
# large_client_header_buffers   4 4k/8k;
### Timeouts, do not keep connections open longer then necessary
### to reduce resource usage and deny Slowloris type attacks.
client_body_timeout             5s;     # maximum time between packets the client can pause when sending nginx any data
client_header_timeout           5s;     # maximum time the client has to send the entire header to nginx
keepalive_timeout               30s;    # timeout which a single keep-alive client connection will stay open
send_timeout                    15s;    # maximum time between packets nginx is allowed to pause when sending the client data
spdy_keepalive_timeout          123s;   # inactivity timeout after which the SPDY connection is closed
spdy_recv_timeout               4s;     # timeout if nginx is currently expecting data from the client but nothing arrives
### General Options
charset                         utf-8;  # adds the line "Content-Type" into response-header, same as "source_charset"
default_type                    application/octet-stream;
gzip                            off;    # disable on the fly gzip compression due to higher latency, only use gzip_static
# gzip_disable                  "msie6";
gzip_static                     on;     # precompress content (gzip -9) with an external script
# gzip_vary                     on;     # send response header "Vary: Accept-Encoding"
gzip_proxied                    any;    # allows compressed responses for any request even from proxies
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.0;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
ignore_invalid_headers          on;
include                         mime.types;
keepalive_requests              50;     # number of requests per connection, does not affect SPDY
keepalive_disable               none;   # allow all browsers to use keepalive connections
max_ranges                      1;      # allow a single range header for resumed downloads and to stop large range header DoS attacks
msie_padding                    off;
open_file_cache                 max=10000 inactive=30s;
open_file_cache_errors          on;
open_file_cache_min_uses        2;
open_file_cache_valid           60s;
pagespeed                       on;
pagespeed                       FileCachePath /var/ngx_pagespeed_cache;
pagespeed MapOriginDomain "" "";       # pagespeed for SSL
#postpone_output                 1460;   # postpone sends to match our machine's MSS
read_ahead                      512K;   # kernel read head set to the output_buffers
recursive_error_pages           on;
reset_timedout_connection       on;     # reset timed out connections freeing ram
server_tokens                   off;    # version number in error pages
# server_names_hash_bucket_size 64;
server_name_in_redirect         off;    # if off, nginx will use the requested Host header
source_charset                  utf-8;  # same value as "charset"
# don't buffer data sent, good for small data bursts in real time
tcp_nodelay                     on;     # Nagle buffering algorithm, used for keepalive only
# send headers in one peace, its better then sending them one by one
tcp_nopush                      on;
types_hash_max_size             2048;
### Allow HTTP method
map $request_method $bad_method {
    default 1;
    ~(?i)(GET|HEAD|POST) 0;
### Block  user_agent
map $http_user_agent $bad_bot {
    default 0;
    ~(?i)(httrack|WinHTTrack|htmlparser|libwww|Python|perl|urllib|Zeus|scan|Curl|email|PycURL|Pyth|PyQ|WebCollector|WebCopier|WebCopy|webcraw|LWP::simple|Havij|^Java|^Jakarta|User-Agent|compatible|lwp-trivial|http\client) 1;
### Block referrers
map $http_referer $bad_referer {
    default 0;
    ~(?i)(babes|click|forsale|jewelry|nudit|organic|poker|porn|amnesty|poweroversoftware|webcam|zippo|casino|replica) 1;
### Limit number of connections per single IP
#limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:1m;
### Limit number of requests for a given session
limit_req_zone  $binary_remote_addr  zone=gulag:1m   rate=60r/m;
### Enable AIO for serving large file
### If these file is very large --> use XFS
### If XFS then "directio 4096"
aio on;
directio 512;
output_buffers 1 128k;
### Logging Settings
log_format  main  '$remote_addr $host $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $ssl_cipher $request_time';
### SSL
# RSA ciphers
# ECDSA ssl ciphers; google chrome prefered order, 128bit most prefered
ssl_ecdh_curve secp521r1;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
#ssl_session_timeout 5m; # SPDY timeout=180sec, keepalive=20sec; connection close=session expires
### User-agent redirect
map $http_user_agent $ua_redirect {
    default '';
#    ~(MSIE|Mozilla);
    ### HTTP server
    server {
        add_header  Cache-Control "public";
        access_log  /var/log/nginx/access.log main buffer=32k;
        error_log   /var/log/nginx/error.log error;
        expires     max;
        listen      80;
        root        /var/empty;
        return 301$uri;
    ### HTTPS server
    server {
        # config to don't allow the browser to render the page inside an frame or iframe
        # and avoid clickjacking
        # if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
        add_header X-Frame-Options DENY;
        # This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
        # It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
        # this particular website if it was disabled by the user.
        add_header X-XSS-Protection "1; mode=block";
        add_header  Cache-Control "public";
        # with Content Security Policy (CSP) enabled(and a browser that supports it(,
        # you can tell the browser that it can only download content from the domains you explicitly allow
        # I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
        # directives for css and js(if you have inline css or js, you will need to keep it too).
        # more:
        #add_header  Content-Security-Policy "default-src 'none';style-src 'self';img-src 'self' data: ;";
        # when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
        # to disable content-type sniffing on some browsers.
        # currently suppoorted in IE > 8
        # 'soon' on Firefox
        add_header  X-Content-Type-Options "nosniff";
        # config to enable HSTS(HTTP Strict Transport Security)
        # to avoid ssl stripping
        add_header  Strict-Transport-Security "max-age=315360000; includeSubdomains";
        access_log  /var/log/nginx/access.log main;
        error_log   /var/log/nginx/error.log info;
        expires     max;
        index       index.php index.html;
        listen      443 ssl spdy;
        root        /usr/local/nginx/html;
        server_name _;
        rewrite ^/(.*\.php)(/)(.*)$ /$1?file=/$3 last;  # For Moodle running
        # client_max_body_size 10000M;                  # For Moodle installation
        ### SSL
        ssl on;
        ssl_certificate      ../ssl/abc.crt;
        ssl_certificate_key  ../ssl/abc.key;
        ssl_ecdh_curve secp521r1;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ### Enable OCSP stapling
        #ssl_stapling on;
        #ssl_trusted_certificate ../ssl/abc.staple;
        if ($bad_bot = 1) { return 444; }
        if ($bad_referer = 1) { return 444; }
        if ($ua_redirect != '') {
            rewrite ^ http://$ua_redirect$request_uri? permanent;
        location / {
            try_files $uri $uri/ =404;
        ### Stub status setting
            location /nginx_status {
            stub_status on;
            access_log   off;
            deny all;
        ## Restricted Access directory by password in the access_list file.
        location ^~ /secure/ {
            deny all;
            auth_basic "RESTRICTED ACCESS";
            auth_basic_user_file /var/www/htdocs/secure/access_list;
        # apple icons, all apple icon requests are served the same local file
        # location ~* /apple-touch-icon(.*)\.png$ { rewrite ^ /apple-touch-icon.png break; }
        ## default location with System Maintenance (Service Unavailable) check
        #location  / { try_files system_maintenance.html $uri $uri/ =404; }
        ## All other errors get the generic error page
        error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 495 496 497
                   500 501 502 503 504 505 506 507 /error_page.html;
        location  /error_page.html { internal; }
        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                fastcgi_pass unix:/var/run/php5-fpm.sock;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_index index.php;
                include fastcgi_params;
                # fastcgi_read_timeout 600;               # For Moodle installation
        location = /robots.txt  { access_log off; log_not_found off; }
        location = /favicon.ico { return 204; access_log off; log_not_found off; }
        ## Disable access to hidden files
        location ~ /\.          { access_log off; log_not_found off; deny all; }
        location ~ ~$           { access_log off; log_not_found off; deny all; }
        location ~*  \.(jpg|jpeg|png|gif|ico)$ { access_log off; log_not_found off; }

A few things to note. I’ve include the core rules file from the Naxsi source. To make sure that file exists, we need to copy it over to/etc/nginx/.

$ sudo cp naxsi-0.53-2/naxci_config/naxsi_core.rule /etc/nginx

Now let’s restart the server so it picks up these changes:

$ sudo service nginx restart

Or, if you took my suggestion of creating an alias, you can just type:xrestart and Nginx will restart itself.

Probably Apache can be tuned to get pretty close to Nginx’s performance with static files, but it’s going to take quite a bit of work. One is not necessarily better, but there are better tools for different jobs.

That said, obviously a CDN service like Cloudfront will, in most cases, be much faster than Nginx or any other server.