Build NGINX 1.10.3 from package sources with added Naxsi, PageSpeed, LDAP and GeoIP on Ubuntu-16.04 Xenial

6 minute read , Sep 29, 2017

The Nginx packages in Ubuntu Xenial do not come with some modules that are one of the most important when setting up Nginx for production use, like LDAP, Naxsi WAF and Pagespeed, just to mention some that I most frequently need and use. This is the process I go through to get these modules into DEB packages.

Build and Install

The work is being done on an AWS EC2 instance with Xenial installed:

# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:   xenial

The current version of Nginx in Xenial is 1.10.3 so I start by downloading the source for it.

cd /tmp
sudo apt build-dep nginx-extras nginx-common
sudo apt-get source nginx-extras
cd nginx-1.10.3/

Install some needed packages:

sudo apt install liblua5.1-0-dev libluajit-5.1-dev daemon dbconfig-common unzip

First I make sure I have the needed modules enabled in nginx-1.10.3/auto/options, change HTTP_GEOIP=NO to HTTP_GEOIP=YES and enable some other modules like DAV, SSL, SUB, XSLT etc. And then pulling in the needed modules like the LDAP one:

cd debian/modules/
git clone https://github.com/kvspb/nginx-auth-ldap.git

then Pagespeed with PSOL:

wget https://github.com/pagespeed/ngx_pagespeed/archive/latest-stable.zip
unzip latest-stable.zip
cd ngx_pagespeed-latest-stable/
wget https://dl.google.com/dl/page-speed/psol/1.12.34.2-x64.tar.gz
tar -xzvf 1.12.34.2-x64.tar.gz
rm 1.12.34.2-x64.tar.gz

and NAXSI too:

cd /tmp 
unzip master.zip 
cp -R naxsi-master/naxsi_src/ nginx-1.10.3/debian/modules/nginx-naxsi

Then I edit the nginx-1.10.3/debian/rules file and add the new modules to the appropriate section:

[...]
extras_configure_flags := \
                        $(common_configure_flags) \
                        --with-http_addition_module \
                        --with-http_dav_module \
[...]
                        --add-module=$(MODULESDIR)/nginx-lua \
                        --add-module=$(MODULESDIR)/nginx-upload-progress \
                        --add-module=$(MODULESDIR)/nginx-upstream-fair \
                        --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module \
                        --add-module=$(MODULESDIR)/nginx-auth-ldap \
                        --add-module=$(MODULESDIR)/ngx_pagespeed-latest-stable \
                        --add-module=$(MODULESDIR)/nginx-naxsi
[...]

Basically add:

--add-module=$(MODULESDIR)/nginx-auth-ldap \
--add-module=$(MODULESDIR)/ngx_pagespeed-latest-stable \
--add-module=$(MODULESDIR)/nginx-naxsi

to all sections for the nginx variants we want to include the modules for. Next is the nginx-1.10.3/debian/changelog file. Substitute the first line at the top:

nginx (1.10.3-0ubuntu0.16.04.2) xenial-security; urgency=medium

with:

nginx (1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi) xenial-security; urgency=medium

Change into the nginx-1.10.3 directory and build the deb packages:

sudo dpkg-buildpackage -uc -b

and install what we need:

$ cd ../
$ ls -1 nginx*naxsi*.deb
nginx_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_all.deb
nginx-common_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_all.deb
nginx-core_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-core-dbg_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-doc_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_all.deb
nginx-extras_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-extras-dbg_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-full_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-full-dbg_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-light_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb
nginx-light-dbg_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb

$ sudo dpkg -i nginx-common_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_all.deb nginx-extras_1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi_amd64.deb

Finally pin the packages so they don’t get overridden on upgrade:

$ dpkg -l | grep nginx
ii  nginx-common     1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi all          small, powerful, scalable web/proxy server - common files
ii  nginx-extras     1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi amd64        nginx web/proxy server (extended version)

Create a new /etc/apt/preferences.d/nginx file:

Package: nginx-common
Pin: version 1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi
Pin-Priority: 1001
 
Package: nginx-extras
Pin: version 1.10.3-0ubuntu0.16.04.2-ldap-pagespeed-naxsi
Pin-Priority: 1001

Configuration

This section was not initially planned to be included but thought it doesn’t hurt to show what my /etc/nginx/nginx.conf file usually looks like:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 512;
    multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    tcp_nopush on;
    tcp_nodelay on;
    client_body_timeout 30;
    client_header_timeout 10;
    keepalive_timeout 65 20;
    types_hash_max_size 2048;
    ignore_invalid_headers on;
    server_names_hash_bucket_size 128;

    client_header_buffer_size   1k;
    client_body_buffer_size     128k;
    large_client_header_buffers 4 4k;

    include                   /etc/nginx/mime.types;
    default_type              application/octet-stream;
    keepalive_requests        100;  
    keepalive_disable         none;
    max_ranges                1;
    msie_padding              off;
    open_file_cache           max=1000 inactive=2h;
    open_file_cache_errors    on;
    open_file_cache_min_uses  1;
    open_file_cache_valid     1h;
    output_buffers            1 512k;
    postpone_output           1460;
    read_ahead                512K;
    #recursive_error_pages     on;
    reset_timedout_connection on;
    sendfile                  on;
    server_tokens             off;
    server_name_in_redirect   off;
    source_charset            utf-8; # same value as "charset"

    ## Request limits
    limit_req_zone  $binary_remote_addr  zone=nginx:1m   rate=1000r/m;

    ##
    # SSL Settings
    ##
    #ssl                       on;
    ssl_session_tickets       on;
    ssl_session_cache         shared:SSL:20m;
    ssl_session_timeout       4h;
    ssl_dhparam               /etc/nginx/ssl/dhparam.pem;
    ssl_ecdh_curve            secp384r1;
    ssl_certificate           /etc/nginx/ssl/star_domain_com.crt;
    ssl_certificate_key       /etc/nginx/ssl/star_domain_com.crt;
    ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers           'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK';

    ##
    # GeoIP
    ##
    geoip_country /usr/share/GeoIP/GeoIP.dat;
    geoip_city /usr/share/GeoIP/GeoLiteCity.dat;

    ##
    # ngx_pagespeed config
    ##
    pagespeed on;
    pagespeed FileCachePath "/var/cache/ngx_pagespeed/";
    pagespeed EnableFilters combine_css,combine_javascript;
    pagespeed PreserveUrlRelativity on;

    ##
    # LDAP
    ##
    auth_ldap_cache_enabled off;
    auth_ldap_cache_expiration_time 10000;
    auth_ldap_cache_size 1000;

    ldap_server ldap1 {
        url ldap://ldap1.domain.com:389/ou=Users,dc=domain,dc=com?uid?sub;
        binddn "cn=some-bind-user,ou=Users,dc=domain,dc=com";
        binddn_passwd some-password;
        group_attribute memberUid;
        group_attribute_is_dn off;
        require group "cn=some-group,ou=Groups,dc=domain,dc=com";
        require valid_user;
    }

    ldap_server ldap2 {
        url ldap://ldap2.domain.com:389/ou=Users,dc=domain,dc=com?uid?sub;
        binddn "cn=some-bind-user,ou=Users,dc=domain,dc=com";
        binddn_passwd some-password;
        group_attribute memberUid;
        group_attribute_is_dn off;
        require group "cn=some-group,ou=Groups,dc=domain,dc=com";
        require valid_user;
    }

    ##
    # Logging Settings
    ##

    ## Log Format
    log_format  main  '$remote_addr $host $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $ssl_cipher $request_time';

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    #gzip_buffers 16 8k;
    gzip_buffers  128 32k;
    #gzip_http_version 1.1;
    #gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_types      text/plain text/css text/x-component
                        text/xml application/xml application/xhtml+xml application/json
                        image/x-icon image/bmp image/svg+xml application/atom+xml
                        text/javascript application/javascript application/x-javascript
                        application/pdf application/postscript
                        application/rtf application/msword
                        application/vnd.ms-powerpoint application/vnd.ms-excel
                        application/vnd.ms-fontobject application/vnd.wap.wml
                        application/x-font-ttf application/x-font-opentype;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Redirest HTTP to HTTPS in the default site /etc/nginx/sites-enabled/default:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    #rewrite ^ https://$host$request_uri permanent;
    return 301 https://$host$request_uri;

    server_name _;
    location / {
        try_files $uri $uri/ =404;
    }

    location ~ /\. {
        deny  all;
    }
    
    location /doc/ {
        alias /usr/share/doc/;
        autoindex on;
        allow 127.0.0.1;
        deny all;
    }

    location /RequestDenied {
       return 418;
    }
}

server {
    ssl    on;
    listen 443 default_server;
    listen [::]:443 default_server;
    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;
}

And the main site config /etc/nginx/sites-enabled/site with some of the mentioned modules configuration (we can enable http2 if our app supports it):

server {
    ssl            on;
    listen         *:443 ssl backlog=1250 so_keepalive=on http2; 
    server_name    site.domain.com www.site.domain.com;
    root           /var/www/html;
    index          index.html index.htm;

    access_log      /var/log/nginx/site-access.log main;
    error_log       /var/log/nginx/site-error.log;

    add_header      X-Content-Type-Options "nosniff";
    #add_header     X-Frame-Options "DENY";
    #add_header     Content-Security-Policy "default-src 'none';style-src 'self';img-src 'self' data: ;";

    # Config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
    # To avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
    add_header      Strict-Transport-Security "max-age=315360000; includeSubdomains";
    limit_req       zone=nginx burst=200 nodelay; # works in conjunction with limit_req_zone set in nginx.conf


    ## Block some nasty robots/bots
    if ($http_user_agent ~ (msnbot|Purebot|Baiduspider|Lipperhey|Mail.Ru|scrapbot|Morfeus|masscan) ) {
        return 403;
    }

    ## Block torrent trackers ddos attacks
    location /announc {
        access_log off;
        error_log off;
        default_type text/plain;
        return 410 "d14:failure reason13:not a tracker8:retry in5:nevere";
    }

    ## Deny scripts inside writable directories
    location ~* /(images|cache|media|logs|tmp)/.*.(php|pl|py|jsp|asp|sh|cgi)$ {
        return 403;
    }

    ## Block HEAD requests
    if ($request_method !~ ^(HEAD)$ ) {
       return 444;
    }

    ## Prevent image hotlinking
    location ~ .(gif|png|jpe?g)$ {
      valid_referers none blocked server_names 127.0.0.1 *.domain.com;
      if ($invalid_referer) {
        return   403;
      }
    }

    ## GeoIP
    if ($geoip_country_code ~ (CN|KR|US|RU|VN|BR|TR) ) {
       return 403;
    }

    #pagespeed FetchHttps enable,allow_self_signed,allow_unknown_certificate_authority,allow_certificate_not_yet_valid;
    #pagespeed SslCertDirectory /etc/nginx/ssl;
    #pagespeed RespectXForwardedProto on;

    #  Ensure requests for pagespeed optimized resources go to the pagespeed
    #  handler and protect some resources
    location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { add_header "" ""; }
    location ~ "^/ngx_pagespeed_static/" { }
    location ~ "^/ngx_pagespeed_beacon" { }
    location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all; }
    location /ngx_pagespeed_global_statistics { allow 127.0.0.1; deny all; }
    location /ngx_pagespeed_message { allow 127.0.0.1; deny all; }
    location /pagespeed_console { allow 127.0.0.1; deny all; }

    # CORS
    if ($http_origin ~* (https?://[^/]*\.domain\.com(:[0-9]+)?)) {  #Test if request is from allowed domain, you can use multiple if
       set $cors "true";                                            #statements to allow multiple domains, simply setting $cors to true in each one.
    }

    # Content protected by LDAP
    location / {
        auth_ldap "Restricted";
        auth_ldap_servers ldap1;
        auth_ldap_servers ldap2;
    }

    location /RequestDenied {
       return 418;
    }
}

For all this to work we make sure we have the above mentioned SSL certificates under /etc/nginx/ssl.

For GeoIP settings to work:

cd /usr/share/GeoIP/
sudo wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
sudo gunzip -d -f GeoLiteCity.dat.gz

To enable Naxsi we create the following /etc/nginx/mysite.rules file:

LearningMode; #Enables learning mode
SecRulesEnabled;
#SecRulesDisabled;
DeniedUrl "/RequestDenied";
## check rules
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

and copy the /tmp/naxsi-master/naxsi_config/naxsi_core.rules file to /etc/nginx/ directory. Then we can include the following line in the http {} section of the nginx.conf:

include /etc/nginx/naxsi_core.rules;

and the following line:

include /etc/nginx/mysite.rules;

in any /etc/nginx/sites-enabled/site.conf file in the location / {} section.

In case you get the following error or warning on startup:

nginx.service: Failed to read PID from file /run/nginx.pid: Invalid argument

this is the workaround:

sudo mkdir -p /etc/systemd/system/nginx.service.d
sudo printf "[Service]\nExecStartPost=/bin/sleep 0.1\n" > /etc/systemd/system/nginx.service.d/override.conf
sudo systemctl daemon-reload 

Finally to start and enable the service:

sudo systemctl restart nginx.service 
sudo ystemctl status -l nginx.service
sudo systemctl enable nginx.service

Leave a Comment