Nginx from scratch

nginx from scratch with pagesspeed and SPDY

In this article we'll learn on how to build nginx from source with pagespeed and SPDY.

Introduction

In this article we're going to install all the needed software to run a full featured web server on CentOS 7 using nginx with pagespeed and SPDY.

We're also going to setup a Drupal vhost at the end of the guide so we can test our new and shiny web server.

This article is lazy mode compatible, just run the command below and you should have a fully working web server in around 5 minutes ;)

sudo bash -c "$(curl -fsSL https://gist.githubusercontent.com/fabioneves/849bbba81932aa730037/raw/7c27427ffb8bc75fa7eb7bf1dd19e284c98ce2d4/nginx-box.sh)"


But, if you want to learn something and do it by yourself, keep reading it!

We'll install the following software:

  • nginx 1.7.12 with ngx_pagespeed and SPDY
  • MariaDB 10
  • PHP 5.6.x with PHP-FPM
  • Memcached
  • Drupal 7.x

The referenced versions in this article were the most recent at the time. If you find that there's a more up-to-date version, you just need to replace it and it should work just fine.
 

Prerequisites

This guide assumes you already have a CentOS 7 system ready, it should be minimal, with no web related software installed (minimal install is best). All this commands and the script provided was tested on a RunAbove CentOS 7 cloud instance.

All the following commands in this guide, should be ran as root.
 

CentOS extra repositories

Before starting, let's add EPEL and Remi repositories so we can get the most up to date versions of the software we're gonna install and also for system update.

EPEL

wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
rpm -Uvh epel-release-7*.rpm

Remi

wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7*.rpm

Enable Remi repository


remi=`ex /etc/yum.repos.d/remi.repo <<-EOF
	   /^\[remi\]
	   /^enabled=
	   s/=0/=1/
	   /^\[remi-php56\]
	   /^enabled=
	   s/=0/=1/
	   wq
EOF

System update

yum update -y

 

Install required packages

yum install -y gcc-c++ pcre-dev pcre-devel zlib-devel make unzip openssl-devel git libxml2-devel libxslt-devel gd-devel perl-ExtUtils-Embed GeoIP-devel libatomic_ops-devel

 

Build nginx from source

So now let's build nginx from source with ngx_pagespeed and SPDY.

download ngx_pagespeed module

git clone https://github.com/pagespeed/ngx_pagespeed.git ngx_pagespeed
cd ngx_pagespeed
wget https://dl.google.com/dl/page-speed/psol/1.9.32.3.tar.gz
tar -xzvf 1.9.32.3.tar.gz
cd ..

download upload progress module

wget https://github.com/masterzen/nginx-upload-progress-module/archive/v0.9.1.tar.gz
tar zxvf v0.9.1.tar.gz

create nginx user

useradd nginx
usermod -s /sbin/nologin nginx

download nginx

wget http://nginx.org/download/nginx-1.7.12.tar.gz
tar zxvf nginx-1.7.12.tar.gz
cd nginx-1.7.12

configure

./configure --user=nginx --group=nginx --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_spdy_module --with-select_module --with-poll_module --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_spdy_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module --with-http_image_filter_module --with-http_geoip_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_auth_request_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_stub_status_module --with-http_perl_module --with-mail --with-mail_ssl_module --with-cpp_test_module --with-cpu-opt=CPU --with-pcre --with-pcre-jit --with-md5-asm --with-sha1-asm --with-zlib-asm=CPU --with-libatomic --with-debug --with-ld-opt="-Wl,-E" --add-module=../ngx_pagespeed --add-module=../nginx-upload-progress-module-0.9.1

build

make && make install

nginx configuration

I like to have all the configuration files for nginx in blocks, meaning that I prefer to have them separated by type of configuration, so we'll create a config file for gzip, pagespeed, core, etc. You can check more about the core nginx configuration in the official website.

main nginx config

cat <<EOF > /etc/nginx/nginx.conf
user  nginx;
worker_processes 1;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}


http {

    include       mime.types;
    default_type  application/octet-stream;

    # configuration files
    include /etc/nginx/conf.d/*.conf;

    # vhosts
    include /etc/nginx/sites-enabled/*;

}
EOF

 

fastcgi params config file

cat <<EOF > /etc/nginx/fastcgi_params
fastcgi_param   QUERY_STRING            \$query_string;
fastcgi_param   REQUEST_METHOD          \$request_method;
fastcgi_param   CONTENT_TYPE            \$content_type;
fastcgi_param   CONTENT_LENGTH          \$content_length;

fastcgi_param   SCRIPT_FILENAME         \$request_filename;
fastcgi_param   SCRIPT_NAME             \$fastcgi_script_name;
fastcgi_param   REQUEST_URI             \$request_uri;
fastcgi_param   DOCUMENT_URI            \$document_uri;
fastcgi_param   DOCUMENT_ROOT           \$document_root;
fastcgi_param   SERVER_PROTOCOL         \$server_protocol;
fastcgi_param   HTTPS                   \$https if_not_empty;

fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
fastcgi_param   SERVER_SOFTWARE         nginx/\$nginx_version;

fastcgi_param   REMOTE_ADDR             \$remote_addr;
fastcgi_param   REMOTE_PORT             \$remote_port;
fastcgi_param   SERVER_ADDR             \$server_addr;
fastcgi_param   SERVER_PORT             \$server_port;
fastcgi_param   SERVER_NAME             \$server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param   REDIRECT_STATUS         200;
EOF

 

create conf.d folder for our config files

mkdir /etc/nginx/conf.d

 

nginx core config

cat <<EOF > /etc/nginx/conf.d/core.conf
## sendfile and tcp_nopush
## - Ensures that the packets are full before sending to the client.
sendfile on;
tcp_nopush on;

## tcp_nodelay
## - Forces the socket to send the data (saving up to 0.2 seconds per file (nagle's algorithm)).
tcp_nodelay on;

## server_tokens
## - Enables or disables emitting nginx version in error messages and in the 'Server' response header field.
server_tokens off;

## client_max_body_size
## - Sets the maximum allowed size of the client request body, specified in the 'Content-Length' request header field.
##   If the size in a request exceeds the configured value, the 413 (Request Entity Too Large) error is returned to the client.
client_max_body_size 256m;

## keepalive_timeout
## - Sets a timeout during which a keep-alive client connection will stay open on the server side (default 75s).
keepalive_timeout 30;

## client_header_timeout
## - Defines a timeout for reading client request header.
client_header_timeout 10;

## client_body_timeout
## - Defines a timeout for reading client request body.
client_body_timeout 10;

## send_timeout
## - Sets a timeout for transmitting a response to the client.
send_timeout 10;
EOF

 

nginx gzip config

cat <<EOF > /etc/nginx/conf.d/gzip.conf
gzip on;
gzip_disable "msie6";
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_min_length 0;
gzip_proxied any;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
EOF

 

nginx pagespeed config

There's a lot to talk about pagespeed configuration. Every pagespeed config should be adapted to your use case/website, this config should be useful as a starting point. You can check more about pagespeed filters and config options here.

cat <<EOF > /etc/nginx/conf.d/pagespeed.conf
pagespeed on;

pagespeed FileCachePath /var/cache/ngx_pagespeed_cache;
pagespeed FileCacheSizeKb            102400;
pagespeed FileCacheCleanIntervalMs   3600000;
pagespeed FileCacheInodeLimit        500000;

pagespeed LRUCacheKbPerProcess     8192;
pagespeed LRUCacheByteLimit        16384;

pagespeed MemcachedServers "127.0.0.1:11211";

pagespeed RewriteLevel PassThrough;
pagespeed EnableFilters remove_comments,collapse_whitespace,rewrite_images,resize_images,resize_rendered_image_dimensions,prioritize_critical_css,insert_dns_prefetch,combine_css,rewrite_css,combine_javascript,rewrite_javascript;

pagespeed RespectVary on;
pagespeed CriticalImagesBeaconEnabled false;

pagespeed StatisticsPath /ngx_pagespeed_statistics;
pagespeed GlobalStatisticsPath /ngx_pagespeed_global_statistics;
pagespeed MessagesPath /ngx_pagespeed_message;
pagespeed ConsolePath /pagespeed_console;
pagespeed AdminPath /pagespeed_admin;
pagespeed GlobalAdminPath /pagespeed_global_admin;

pagespeed MessageBufferSize 200000;
pagespeed Statistics on;
pagespeed StatisticsLogging on;
pagespeed LogDir /var/log/pagespeed;
pagespeed StatisticsLoggingIntervalMs 60000;
pagespeed StatisticsLoggingMaxFileSizeKb 1024;
EOF


Special attention for the ngx_pagespeed admin pages, you should block them from the public in your vhost configuration. You can read more about the pagespeed admin pages here.

Here's an example on how to block the statistics page:

location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all; }

 

systemd

CentOS 7 uses the new systemd, so let's create a service for our newly compiled nginx. Run the following command:

cat >> /usr/lib/systemd/system/nginx.service << NGINX_SERVICE  
[Unit]
Description=The nginx HTTP and reverse proxy server  
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking  
PIDFile=/run/nginx.pid  
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf  
ExecReload=/bin/kill -s HUP $MAINPID  
ExecStop=/bin/kill -s QUIT $MAINPID  
PrivateTmp=true

[Install]
WantedBy=multi-user.target
NGINX_SERVICE

 

Enable the service

systemctl enable nginx

 

virtual host config directories

Create the directories for our virtual host configurations.

mkdir /etc/nginx/sites-available /etc/nginx/sites-enabled


Ok, we've finished the nginx build and config.
 

Install PHP and memcached

Just run the following yum command to install PHP with zend opcache, together and some other PHP extensions, feel free to add as many as you want/need. Memcached will also be installed.

yum install -y php php-gd php-pdo php-fpm php-pecl-zendopcache php-mbstring php-mysql php-pecl-uploadprogress memcached


We now have PHP with PHP-FPM installed, let's create our global php-fpm socket to run PHP in our vhosts.

cat <<EOF > /etc/php-fpm.d/global-pool.conf	
[global-pool]
user = nginx
group = nginx
listen = /var/run/php-fpm/php-fpm-global.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
pm = dynamic
pm.start_servers = 1
pm.max_children = 5
pm.min_spare_servers = 1
pm.max_spare_servers = 5
EOF


We should change some of the PHP default settings such as memory_limit, upload size, etc. Here's a command that will apply the following settings to /etc/php.ini config file:

  • date time zone to Europe/Lisbon
  • memory limit to 256M
  • upload size limit to 2GB
  • max input and exec time to 300 seconds
  • max input vars to 3000
php_config=`ex /etc/php.ini <<-EOF
/^post_max_size
s/= 8M/= 2048M/
/^upload_max_filesize
s/= 2M/= 2048M/
/^; max_input_vars
s/= 1000/= 1000\rmax_input_vars = 3000/
/^memory_limit
s/= 128M/= 256M/
/^max_input_time
s/= 60/= 300/
/^max_execution_time
s/= 30/= 300/
/^\[Date\]
/^;date.timezone
s/=/=\rdate.timezone = Europe\/Lisbon/
wq
EOF`


As with all the config files, you're free to choose the values as you prefer/like, this is by no means a "mandatory" config for running your web server. You should always adapt the configuration to the purpose of the server and it's websites.

zend opcache

The zend opcache configuration file is found here: /etc/php-zts.d/10-opcache.ini and it's enabled by default. If you want to disable it, just change the opcache.enable=1 value to 0.
 

Install MariaDB 10

MariaDB is designed as a drop-in replacement of MySQL(R) with more features, new storage engines, fewer bugs, and better performance.

add MariaDB official centos repo

cat >> /etc/yum.repos.d/mariadb.repo << MARIADB
# MariaDB 10.0 CentOS repository list - created 2015-04-15 11:21 UTC
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
MARIADB


install MariaDB

yum install -y MariaDB-server MariaDB-client


start MariaDB server

service mysql start


Now we have to secure your MariaDB installation with the command (DON'T skip this step):

mysql_secure_installation

 

Enable the services

Ok, right now we have nginxphp, memcached and mariadb installed. We should enable all our services, so they're started on the server boot.

systemctl disable httpd
systemctl enable nginx
systemctl enable php-fpm
systemctl enable memcached
service restart nginx
service restart php-fpm
service restart memcached


You might have noticed that we disabled httpd service, yep that's Apache. When we install php through yum, httpd is a dependency so it's installed together with php, as we're not gonna use apache, let's just disable and forget it exists ;)

MariaDB uses the old init.d scripts, when we installed it, it was set to start on server boot, so we don't need to do anything about that. You can check this by typing chkconfig on console.

So now, all we have left to do is to install drupal, you can skip this step if you want as you already have your web server running and ready to roll. Anyway, even if you don't want to install drupal you should at least read the next section as it explains on how to setup a vhost.
 

Install drupal

First let's create a simple vhost configuration for our drupal website including SPDY configuration. As I said, this is a simple configuration, you can customise it much more, I recommend you to check perusio's github repo about drupal with nginx.

First let's generate a self-signed certificate so we can test/use the vhost using SPDY.

mkdir /etc/nginx/ssl
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=localhost" -keyout /etc/nginx/ssl/localhost.key -out /etc/nginx/ssl/localhost.crt

 

vhost configuration

You should replace localhost with your domain name.

cat <<EOF > /etc/nginx/sites-available/drupal.conf
server {

    client_max_body_size 64M;

    listen 80;
    server_name localhost;
    root /home/nginx/drupal;

    index index.php;

    charset utf-8;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

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

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
        add_header Pragma public;
        add_header Cache-Control "public";
        try_files \$uri =404;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm-global.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

}

server {

    client_max_body_size 64M;

    listen 443 ssl spdy;
    server_name localhost;
    root /home/nginx/drupal;

    ssl_certificate /etc/nginx/ssl/localhost.crt;
    ssl_certificate_key /etc/nginx/ssl/localhost.key;

    index index.php;

    charset utf-8;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

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

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
        add_header Pragma public;
        add_header Cache-Control "public";
        try_files \$uri =404;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm-global.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

}
EOF


Once you have the vhost ready, add this extra line:

pagespeed LoadFromFile "http://<SERVER_IP or DOMAIN>/" "/home/nginx/drupal/";


This will make pagespeed work much better with your static content, sometimes it can't find it and fails to apply the filters as it should.

Don't forget to link your vhost config, everytime you create a new one.

ln -s /etc/nginx/sites-available/drupal.conf /etc/nginx/sites-enabled/drupal.conf


install drush

Drush is an awesome cli and it's essential for any drupal developer. We're going to install drupal with drush.

pear channel-discover pear.drush.org
pear install drush/drush


create database to install drupal

We need to create a database for our drupal installation. Replace <MYSQL_ROOT_PASSWORD> for the root password you've set, when you secured MariaDB server installation and <DRUPAL_DB_PASSWORD> with the password you want for your new drupal database (yes, also replace <>).

mysql -uroot -p<MYSQL_ROOT_PASSWORD> -e "create database drupal"
mysql -uroot -p<MYSQL_ROOT_PASSWORD> -e "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON drupal.* TO 'drupal'@'localhost' IDENTIFIED BY '<DRUPAL_DB_PASSWORD>'"


install drupal with drush

We're going to setup our drupal install in /home/nginx. You can choose to place your vhosts anywhere you like as long as nginx have access to it. Replace <DRUPAL_DB_PASSWORD> with your drupal db password.

cd /home/nginx
drush dl drupal --drupal-project-rename=drupal
cd drupal
drush site-install standard --db-url="mysql://drupal:<DRUPAL_DB_PASSWORD>@localhost/drupal" --site-name=Drupal -y
drush dis overlay -y
chown -R nginx:nginx /home/nginx/drupal


If everything went well, you should have drupal installed without that horrendous overlay module. The user and password should be printed to the console.

To access your website just type http://<SERVER_IP> in your browser. Use https for SPDY greatness.
 

Conclusion

Ok, that's it, hope you learned something from my guide. You should have a pretty solid base to play with pagespeed and SPDY by now. If you have any questions or feedback feel free to post in the comments.

Take care.

Fábio Neves

Fábio Neves

Les flere artikler fra Fábio Neves.

Developer and SysAdmin enthusiast at Frontkom, always looking to learn more and have fun.