Building Out a Basic LEMP Server

The LEMP server stack (Linux Nginx MariaDB/MySQL PHP) is the core of many web applications…I use it often on DigitalOcean.  This guide will take you from a basic server setup to a secure LEMP server is a matter of minutes. The following is a sequence of commands that I’ve compiled for myself from several places on the web.

For steps for getting a base server running look at DigitalOcean Droplet Initial Setup.

Customize this Guide

There are a few portions of this guide that you will probably want to customize. If you modify those values here, you will then be able to copy/run the commands without the need for modification.

Website Name (internal):

Site Domain Name:

Installing MariaDB

The LEMP stack database is MySQL, but for the sake choosing open source, we will use MariaDB which is a completely compatible, open alternative. We will add the MariaDB repository, install the database, and secure it.

sudo apt-get install -y software-properties-common
sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
sudo add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu trusty main'
sudo apt-get update && sudo apt-get install -y mariadb-server
sudo mysql_secure_installation

Installing PHP

Next we’ll add the PHP. This PPA has versions 5.5, 5.6, and 7.0…I’ll use 7.0.

sudo locale-gen en_US.UTF-8
export LANG=en_US.UTF-8
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update && sudo apt-get -y install php7.0-fpm php7.0-cli php7.0-mysqlnd

Next we’ll do some basic configuration of PHP-FPM and create a dedicated user/group that it will run under for this website. It’s a good idea to run separate PHP-FPM instances per website and to use unique users/groups. This way if you have multiple sites on one server and one gets compromised, the attack will hopefully be easier to contain.

sudo nano /etc/php/7.0/fpm/php.ini
date.timezone = "America/Chicago"
sudo groupadd 
sudo useradd -g
sudo nano /etc/php/7.0/fpm/pool.d/.conf

[] user =
group =
listen = /run/php/php7.0-fpm-.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

Disabling opcache is another security step you might want to consider depending on you environment (CVE-2015-1351).

sudo nano /etc/php/7.0/fpm/conf.d/10-opcache.ini
opcache.enable=0
sudo rm /etc/php/7.0/fpm/pool.d/www.conf
sudo service php7.0-fpm restart

Installing Nginx

First, add the Nginx repository and then configure a basic, unsecured website.

sudo add-apt-repository -y ppa:nginx/stable
sudo apt-get update && sudo apt-get -y install nginx
sudo mkdir -p /srv/http/
sudo nano /etc/nginx/sites-available/

server {
listen 80;
server_name ;

root /srv/http/;
index index.php index.html index.htm;

location ~ /.well-known {
allow all;
}

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

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.0-fpm-.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/

We’ll create a index.php file just so we have something to test with…you’ll want to replace or delete this file once your LEMP server is up and running.

echo "<?php phpinfo(); ?>" | sudo tee /srv/http//index.php
sudo chown -R :www-data /srv/http/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo service nginx restart

Installing SSL with Let’s Encrypt

Now that we have the web server running, we will get a free SSL certificate from the Let’s Encrypt project and modify Nginx to only communicate over SSL.

sudo apt-get -y install git bc
sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
cd /opt/letsencrypt/

./letsencrypt-auto certonly -a webroot --webroot-path=/srv/http// -d

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

sudo nano /etc/nginx/sites-available/

server {
listen 80;
server_name ;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name ;

ssl_certificate /etc/letsencrypt/live//fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live//privkey.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256: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-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security max-age=15768000;

root /srv/http/;
index index.php index.html index.htm;

location ~ /.well-known {
allow all;
}

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

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.0-fpm-.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

sudo service nginx reload

To test your SSL configuration, point a web browser at:
https://www.ssllabs.com/ssltest/analyze.html?d=

Auto Update Your SSL Certificates

Since Let’s Encrypt certificates have short lifespans, it’s a good idea to update them automatically. We’ll setup a cron job to attempt renewal every month.

sudo crontab -e

30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log
35 2 * * 1 /etc/init.d/nginx reload

Conclusion

That’s it…you now have a fully functional LEMP server running in your Digital Ocean droplet. If you have any comments or suggestions on how to improve this setup please leave me a comment.

Leave a Reply

Your email address will not be published. Required fields are marked *