Garten Eden

#linux #programming #android #online #stuff

A fast and secure web server with nginx on a Raspberry Pi

Everybody who wants to run a brisk, light-weight web server currently is well advised to choose ngnix instead of Apache. The functions are alike and for quite a time the free HTTP server plays a major role in the Internet business (with a market share of 12% according to Netcraft).

For sure the world wide web demands different security levels from servers than a local server. So we welcome nginx (with some precautions we have to arrange at first) scoring well in this field, too. As we do not want to deliver only static content, we will install PHP which we will quicken with XCache and also harden with some option changes.

SFTP instead of FTP

I think it is no news that the unsafe FTP is obsolete. There is a time-tested alternative with SFTP, since it is available for all systems, either with board tools like in GNOME (Places → Connect to Server…) or with popular tools like FileZilla. SFTP uses an encrypted connection over the SSH service which runs by default and rightly on Rasperrys Pis.

Connecting to the RPi

Since there is no need for a screen at the RPi, we will connect to it via another computers SSH client (of course you have to change the variables).

ssh IP_ADDRESS -l USERNAME

Installation and Backup

Next we will install the web server, PHP and the PHP accelerator. I chose the light variant of ngnix out of the Debian packages because the included modules are sufficient for my needs and more stuff leads to more insecurity. Whoever needs more, picks the extended packages.

sudo apt-get install nginx-light php5-fpm php5-xcache

Just to be on the safe side, we will backup every file we will change lateron.

sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bku
sudo cp /etc/nginx/fastcgi_params /etc/nginx/fastcgi_params.bku
sudo cp /etc/php5/fpm/php-fpm.conf /etc/php5/fpm/php-fpm.conf.bku
sudo cp /etc/php5/fpm/php.ini /etc/php5/fpm/php.ini.bku

Edit Files

In order to edit the following config files, I recommend to open the files locally (e.g. with gedit) and copy them afterwards in the Terminal with sudo in the corresponding directories. We will change the user file and folder rights at the end.

Design

All server related files should be located in the /var/www/ directory. The sub-directory nignx/ is for the web server, php-fpm/ for PHP, sockets/ will contain the Unix socket links for PHP (so to speak the connection ports for the PHP engine which every nginx site will connect to) and finally every site will have its own sub-directory.
In my case there is only the site rpidisplay which controls the display of my RPi.
All files accessible via Internet are located in SITE/docs/, so here we have /var/www/rpidisplay/docs/. The corresponding log files are in SITE/logs/. The other directories are only for the sake of completeness. So the full directory tree looks like this:

/var/www/
├── nginx/
│   └── rpidisplay.conf
├── php-fpm/
│   └── rpidisplay.conf
├── rpidisplay/
│   ├── bin/
│   ├── docs/
│   │   └── index.html
│   ├── home/
│   ├── local/
│   │   └── bin
│   ├── logs/
│   │   └── access.log
│   │   └── error.log
│   ├── tmp/
│   └── usr/
│       ├── bin/
│       └── share/
└── sockets/
    └── rpidisplay.socket

Configuring nginx

Due to security reasons our web server will get an own user called nginx who wont have any write permissions to the rest of the system. Besides, non-standard HTTP requests will be dropped and as the RPi has only one core, one worker_process is sufficient. Details on the optional gzip compression (which serve only for performance) you will find further down. Note that this the default configuration file for all sites running. The include line loads advanced configurations for every virtual host.

My nginx config looks like that:
/etc/nginx/nginx.conf:

user nginx nginx;
worker_processes 1;
pid /var/run/nginx.pid;

events {
	worker_connections 768;
}

http {

	##
	# Basic
	##

	sendfile on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	server_tokens off;
	ignore_invalid_headers on;
	server_name_in_redirect off;
	tcp_nodelay off;
	tcp_nopush on;

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


	##
	# Logging 
	##

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


	##
	# Gzip
	##

	gzip on;
	gzip_static on;
	gzip_disable "msie6";

	gzip_vary on;
	gzip_proxied any;
	gzip_comp_level 3;
	gzip_buffers 16 8k;
	gzip_http_version 1.1;
	gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml rss text/javascript text/mathml application/atom xml application/xhtml xml image/svg xml;


	##
	# Virtual Host Configs
	##

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

Configuring FastCGI

FastCGI manages loading external modules into the web server, in our case the php-fpm module. The most important parameters for FastCGI are DOCUMENT_ROOT and SCRIPT_FILENAME. And as I do not want others to read my nginx version, I cut SERVER_SOFTWARE.
/etc/nginx/fastcgi_params:

fastcgi_index index.php;

fastcgi_connect_timeout 65;
fastcgi_send_timeout    180;
fastcgi_read_timeout    180;

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	DOCUMENT_ROOT		/docs;
fastcgi_param	SCRIPT_FILENAME		/docs$fastcgi_script_name;
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	GATEWAY_INTERFACE	CGI/1.1;
fastcgi_param	SERVER_SOFTWARE		nginx;

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;

fastcgi_param	HTTPS			$https;

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

Creating folders and users

Lets create the directories, users and groups we need. There is at least one user and the group nginx and if you use more than one site: an additional group and user per site. Next, nginx has to be added to the sites group so that the web server is able to deliver the files. The Unix command adduser creates an user and the corresponding group, hence the commands are the following:

sudo adduser --no-create-home --disabled-login nginx
sudo adduser --no-create-home --disabled-login rpidisplay
sudo usermod -G rpidisplay nginx

Now we create the directories. First the ones for all sites, then the one for my rpidisplay host.

sudo mkdir /var/www/ /var/www/sockets/ /var/www/nginx/ /var/www/php-fpm/
cd /var/www/
sudo mkdir rpidisplay/ rpidisplay/bin/ rpidisplay/docs/ rpidisplay/home/ rpidisplay/logs/ rpidisplay/tmp/ rpidisplay/usr/ rpidisplay/usr/bin/ rpidisplay/usr/share/ rpidisplay/local/ rpidisplay/local/bin/

Site-specific configuration

How the sites acts and which content it serves is determined in the specific configuration file. It is called by nginx (as mentioned above). It defines on which port nginx listens and to which server name it is responsive. Plus, the config file defines the path for the log files and the root path.

Additionally, I do not want the server to deliver files with leading dots. The PHP abstract activates forwarding to the PHP socket. And the last part defines the cache duration for static content. In my case the value is quite high as my non-dynamic files do not alter often.
/var/www/nginx/rpidisplay.conf:

server { 
	listen 80;
	server_name rpidisplay;

	access_log /var/www/rpidisplay/logs/access.log;
	error_log /var/www/rpidisplay/logs/error.log warn;

	root /var/www/rpidisplay/docs;


	# deny access to dot-files
	location ^~ /\\. {
		access_log off;
		log_not_found off;
		deny all;
	}


	# enable PHP
	location ~ ^(.*)\\.php$ {
		if (!-f $request_filename) {
			return 404;
		}

		include /etc/nginx/fastcgi_params;
		fastcgi_pass unix:/var/www/sockets/rpidisplay.socket;
	}


	# enable browser cache control for static files including html
	location ~* \\.(html|htm|jpg|jpeg|gif|png|css|js|ico|xml)$ {
		access_log off;
		log_not_found off;
		expires 360d;
	}
}

Configuring PHP

Time for PHP itself. In order to have a nice overview and a convinient way to change PHP options, I prefer its config files in /var/www. So we command PHP-FPM (which supplies the socket PHP is listening to) to look there for config files. This is done in /etc/php5/fpm/php-fpm.conf via adding this line:

include=/var/www/php-fpm/*.conf

Renaming the default config file deactivates it.

sudo mv /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/www.conf.bku

Now we create the FPM configurations and begin with the sites name and the path to the socket we want to open, followed by file permissions and some variables. The path of the socket has to match to the one nginx tries to access (see above) and the user and group permissions fit the ones of our created user for the site.
/var/www/php-fpm/rpidisplay.conf:

[rpidisplay] 
listen = /var/www/sockets/rpidisplay.socket 
listen.owner = rpidisplay 
listen.group = rpidisplay 
listen.mode = 0666 

listen.backlog = -1 
listen.allowed_clients = 127.0.0.1 

user = rpidisplay 
group = rpidisplay 

pm = dynamic 
pm.max_children = 5 
pm.start_servers = 2 
pm.min_spare_servers = 1 
pm.max_spare_servers = 3 
pm.max_requests = 500 

chroot = /var/www/rpidisplay 
chdir = /docs 
 
env[HOSTNAME] = $HOSTNAME 
env[PATH] = /usr/local/bin:/usr/bin:/bin 
env[TMP] = /tmp 
env[TMPDIR] = /tmp 
env[TEMP] = /tmp 
env[HOME] = /home 
env[DOCUMENT_ROOT] = /docs

Last but not least we need the well-known php.ini which needs be hardened. In newer PHP versions many options are already quite safe. At your discretion you can surpress warnings and errors. Some operating system functions should be deactivated in any case in order to complicate server attacks.
/etc/php5/fpm/php.ini:

disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,exec,system,passthru,shell_exec,popen,escapeshellcmd,proc_open,proc_nice,ini_restore

Solve PHP’s timezone error

Because of the restricted (safer) permissions PHP is running now some operating system accesses may not work correctly anymore, e.g. some time functions. Our PHP user is not able to access /usr/share/zoneinfo/ but you can solve this easily by copying the files to the corresponding path in the users root directory.

sudo cp -aR /usr/share/zoneinfo /var/www/rpidisplay/usr/share/

Final touch

The last thing we have to do is deactivating the default site of nginx by removing it from the main path of active sites. (As our sites are located in /var/www/ now.)

sudo rm /etc/nginx/sites-enabled/default

Besides, we change the file and directory permissions. All files in docs/ have to be readable by the sites user, in my case by rpidisplay; then nginx will be able to do the same as it is in the same group.

sudo chown -R rpidisplay:rpidisplay /var/www/rpidisplay/docs/

Finally we restart nginx and PHP for the changes to be read.

sudo service nginx restart 
sudo service php5-fpm restart

If there are no errors our Debian system now runs a fast, light-weight, dynamic, feature-rich and especially secure web server.
For database needs I recommend SQLite.

Why gzip compression?

In the main config file we activated gzip compression. This option enables the web server to search for a compressed version of a file and deliver it. This means: If index.html is accessed, the web server checks whether index.html.gz exists and if yes delivers it instead of the bigger file. Therefore the transmission size decreases, thats why I activated it for all static files.

Limitations: nginx does not compress on-demand, so you have to do it yourself for every file you alter. On the other hand this allows you to always use the highest compression mode without putting load on your server. Besides, there are rumours that this behaviour can lead to errors if the original file and its compressed version do not have the same last-modified date. I recommend touching all files after compression.

Sources

These sites were my inspiration for this article:

Comments

Your comment:






So far...

...no comments

en

Wait a sec, loading...

Email (not published)

Homepage (optional)

Comment... (code tag allowed)