LEMP: Linux NGINX MySQL PHP Server Configuration Walkthrough

Initial Server Setup

This is a quick and gritty guide to setting up a Linux based server for MySQL, PHP, NGINX.

Logging in as Root

To log into your server, you will need to know your server’s public IP address. You will also need the password or, if you installed an SSH key for authentication, the private key for the root user’s account. If you have not already logged into your server, you may want to follow our guide on how to connect to your Droplet with SSH, which covers this process in detail.

If you are not already connected to your server, go ahead and log in as the root user using the following command (substitute the highlighted portion of the command with your server’s public IP address):

ssh root@your_server_ip

First, Update

apt update
apt upgrade

Accept the warning about host authenticity if it appears. If you are using password authentication, provide your root password to log in. If you are using an SSH key that is passphrase protected, you may be prompted to enter the passphrase the first time you use the key each session. If this is your first time logging into the server with a password, you may also be prompted to change the root password.

Creating a New User

Once you are logged in as root, we’re prepared to add the new user account that we will use to log in from now on.

This example creates a new user:

adduser USERNAME

You will be asked a few questions, starting with the account password.

Enter a strong password and, optionally, fill in any of the additional information if you would like. This is not required and you can just hit ENTER in any field you wish to skip.

Granting Administrative Privileges

Now, we have a new user account with regular account privileges. However, we may sometimes need to do administrative tasks.

To avoid having to log out of our normal user and log back in as the root account, we can set up what is known as “superuser” or root privileges for our normal account. This will allow our normal user to run commands with administrative privileges by putting the word sudo before each command.

To add these privileges to our new user, we need to add the new user to the sudo group. By default, on Ubuntu 18.04, users who belong to the sudo group are allowed to use the sudo command.

As root, run this command to add your new user to the sudo group (substitute the highlighted word with your new user):

usermod -aG sudo USERNAME

Now, when logged in as your regular user, you can type sudo before commands to perform actions with superuser privileges.

Setting Up a Basic Firewall

Ubuntu 18.04 servers can use the UFW firewall to make sure only connections to certain services are allowed. We can set up a basic firewall very easily using this application.

Different applications can register their profiles with UFW upon installation. These profiles allow UFW to manage these applications by name. OpenSSH, the service allowing us to connect to our server now, has a profile registered with UFW.

You can see this by typing:

ufw app list

Output

Available applications:
  OpenSSH

We need to make sure that the firewall allows SSH connections so that we can log back in next time. We can allow these connections by typing:

ufw allow OpenSSH

Afterwards, we can enable the firewall by typing:

ufw enable

Type “y” and press ENTER to proceed. You can see that SSH connections are still allowed by typing:

ufw status

Output:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Next let’s move those keys over to the new account. From root:

mkdir /home/USERNAME/.ssh
cp ~/.ssh/authorized_keys /home/USERNAME/.ssh
chown -R USERNAME /home/USERNAME/.ssh

Next, switch to the new user and generate an SSH Key for Github/BitBucket access and paste on the company Github (https://github.com/settings/keys) and company BitBucket (https://bitbucket.org/###########/#########/admin/access-keys/):

ssh-keygen
cat /home/USERNAME/.ssh/id_rsa.pub

Introduction

The LEMP software stack is a group of software that can be used to serve dynamic web pages and web applications. This is an acronym that describes a Linux operating system, with an Nginx (pronounced like “Engine-X”) web server. The backend data is stored in the MySQL database and the dynamic processing is handled by PHP.

This guide demonstrates how to install a LEMP stack on an Ubuntu 18.04 server. The Ubuntu operating system takes care of the first requirement. We will describe how to get the rest of the components up and running.

Prerequisites

Before you complete this tutorial, you should have a regular, non-root user account on your server with sudo privileges. Set up this account by completing our initial server setup guide for Ubuntu 18.04.

Once you have your user available, you are ready to begin the steps outlined in this guide.

Make Sure Root Login Disabled (if not from previous step)

sudo vim /etc/ssh/sshd_config

PermitRootLogin needs to be set no

sudo service ssh restart

or:

sudo passwd -l root

Make Sure Things Are Up To Date

Enable automatic updates can be crucial for your server security. It is very important to stay up to date.

sudo apt-get install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

IP hardening

We also want to setup some IP settings for security. This can be done in:

sudo vim /etc/sysctl.d/50-ip-sec.conf

The configuration file: disables ICMP redirects, enables IP spoofing protection, ignores broadcast requests and so on. We disable IPv6 auto configuration, because it’s manually configured in the step before. Some more information about the various options can be found in the redhat docs - Securing Network Access.

# Disable Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Disable acceptance of all ICMP redirected packets on all interfaces
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Disable send IPv4 redirect packets
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Set Reverse Path Forwarding to strict mode as defined in RFC 3704
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Block pings
net.ipv4.icmp_echo_ignore_all = 1

# Syn flood help
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Log suspicious martian packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians=1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Disable IPv6 auto config
net.ipv6.conf.default.accept_ra=0
net.ipv6.conf.default.autoconf=0
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.all.autoconf=0
net.ipv6.conf.eth0.accept_ra=0
net.ipv6.conf.eth0.autoconf=0

After you’ve created / changed this file, apply the settings:

sudo sysctl --system

Installing the Nginx Web Server

Since this is our first time using apt for this session, start off by updating your server’s package index. Following that, install the server

sudo apt install nginx

On Ubuntu 18.04, Nginx is configured to start running upon installation.

If you have the apt firewall running, as outlined in the initial setup guide, you will need to allow connections to Nginx. Nginx registers itself with apt

It is recommended that you enable the most restrictive profile that will still allow the traffic you want. Since you haven’t configured SSL for your server in this guide, you will only need to allow traffic on port 80.

sudo ufw allow 'Nginx HTTP'

You can verify the change by running:

sudo ufw status

This command’s output will show that HTTP traffic is allowed:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx HTTP                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx HTTP (v6)            ALLOW       Anywhere (v6)

ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

or

curl -4 icanhazip.com

Type the address that you receive in your web browser and it will take you to Nginx’s default landing page:

http://server\_domain\_or\_IP

If you see the above page, you have successfully installed Nginx.


Installing NGINX Security:

If you have a bizarre or complicated setup, be sure to look everything over before installing. However, for anyone with a fairly straightforward Nginx installation, this should work without any issues.

Step 1. Clone the Nginx Bad Bot Blocker repository into your Nginx directory. https://github.com/mariusv/nginx-badbot-blocker; has to be sudo.

cd /etc/nginx
sudo git clone https://github.com/mariusv/nginx-badbot-blocker.git

Step 2. Edit /etc/nginx/nginx.conf and add the following at the end of the http block, before the closing }

sudo vim /etc/nginx/nginx.conf
##
# Nginx Bad Bot Blocker
##
include nginx-badbot-blocker/blacklist.conf;
include nginx-badbot-blocker/blockips.conf;

Step 3. Run the following command to verify your Nginx configuration is valid. (Note: You may need to prepend sudo to this command.)

You should get an output that looks something like this:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Step 4. Reload Nginx and you’re all set!

sudo service nginx reload

Installing MySQL to Manage Site Data

Now that you have a web server, you need to install MySQL (a database management system) to store and manage the data for your site. Install MySQL by typing:

sudo apt install mysql-server

The MySQL database software is now installed, but its configuration is not yet complete.

To secure the installation, MySQL comes with a script that will ask whether we want to modify some insecure defaults. Initiate the script by typing:

sudo mysql_secure_installation

This script will ask you to supply a password for use within the MySQL system. After this, it will ask if you want to configure the VALIDATE PASSWORD PLUGIN.

Warning: Enabling this feature is something of a judgment call. If enabled, passwords which don’t match the specified criteria will be rejected by MySQL with an error. This will cause issues if you use a weak password in conjunction with software which automatically configures MySQL user credentials, such as the Ubuntu packages for phpMyAdmin. It is safe to leave validation disabled, but you should always use strong, unique passwords for database credentials.

Answer Y for yes, or anything else to continue without enabling.

VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No:

If you’ve enabled validation, the script will also ask you to select a level of password validation. Keep in mind that if you enter 2 – for the strongest level – you will receive errors when attempting to set any password which does not contain numbers, upper and lowercase letters, and special characters, or which is based on common dictionary words.

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1

If you enabled password validation, you’ll be shown a password strength for the existing root password, and asked you if you want to change that password. If you are happy with your current password, enter N for “no” at the prompt:

Using existing password for root. Estimated strength of the password: 100 Change the password for root ? ((Press y|Y for Yes, any other key for No) : nFor the rest of the questions, you should press Y and hit the ENTER key at each prompt. This will remove some anonymous users and the test database, disable remote root logins, and load these new rules so that MySQL immediately respects the changes we have made.Note that in Ubuntu systems running MySQL 5.7 (and later versions), the root MySQL user is set to authenticate using the auth_socket plugin by default rather than with a password. This allows for some greater security and usability in many cases, but it can also complicate things when you need to allow an external program (e.g., phpMyAdmin) to access the user.

If you prefer to use a password when connecting to MySQL as root, you will need to switch its authentication method from auth_socket to mysql_native_password. To do this, open up the MySQL prompt from your terminal:

sudo mysql

Next, check which authentication method each of your MySQL user accounts use with the following command:

Please set the password for root here.

New password:

Re-enter new password:

For the rest of the questions, you should press Y and hit the ENTER key at each prompt. This will remove some anonymous users and the test database, disable remote root logins, and load these new rules so that MySQL immediately respects the changes we have made.

Note that in Ubuntu systems running MySQL 5.7 (and later versions), the root MySQL user is set to authenticate using the auth_socket plugin by default rather than with a password. This allows for some greater security and usability in many cases, but it can also complicate things when you need to allow an external program (e.g., phpMyAdmin) to access the user.

If you prefer to use a password when connecting to MySQL as root, you will need to switch its authentication method from auth_socket to mysql_native_password. To do this, open up the MySQL prompt from your terminal:

sudo mysql

Next, check which authentication method each of your MySQL user accounts use with the following command:

SELECT user,authentication_string,plugin,host FROM mysql.user;

Output

+------------------+-------------------------------------------+-----------------------+-----------+
| user             | authentication_string                     | plugin                | host      |
+------------------+-------------------------------------------+-----------------------+-----------+
| root             |                                           | auth_socket           | localhost |
| mysql.session    | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| mysql.sys        | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost |
+------------------+-------------------------------------------+-----------------------+-----------+
4 rows in set (0.00 sec)

In this example, you can see that the root user does in fact authenticate using the auth_socket plugin. To configure the root account to authenticate with a password, run the following ALTER USER command. Be sure to change password to a strong password of your choosing:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

Then, run FLUSH PRIVILEGES which tells the server to reload the grant tables and put your new changes into effect:

FLUSH PRIVILEGES;

Check the authentication methods employed by each of your users again to confirm that root no longer authenticates using the auth socket plugin:

SELECT user,authentication_string,plugin,host FROM mysql.user;

Output:

+------------------+-------------------------------------------+-----------------------+-----------+
| user             | authentication_string                     | plugin                | host      |
+------------------+-------------------------------------------+-----------------------+-----------+
| root             | *3636DACC8616D997782ADD0839F92C1571D6D78F | mysql_native_password | localhost |
| mysql.session    | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| mysql.sys        | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost |
| debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost |
+------------------+-------------------------------------------+-----------------------+-----------+
4 rows in set (0.00 sec)

You can see in this example output that the root MySQL user now authenticates using a password. Once you confirm this on your own server, you can exit the MySQL shell:

exit

At this point, your database system is now set up and you can move on to installing PHP.

Create Database

sudo mysql

In MySQL, we’re going to create a new user and a new database:

create database @@@;
create user '###' identified by '###';
grant all privileges on @@@.* to '###';
flush privileges;
exit

Installing PHP for Processing

You now have Nginx installed to serve your pages and MySQL installed to store and manage your data. However, you still don’t have anything that can generate dynamic content. This is where PHP comes into play.

Since Nginx does not contain native PHP processing like some other web servers, you will need to install php-fpm, which stands for “fastCGI process manager”. We will tell Nginx to pass PHP requests to this software for processing.

Install this module along with an additional helper package that will allow PHP to communicate with your database backend. The installation will pull in the necessary PHP core files. Do this by typing:

sudo apt install php-fpm php-mysql php-gd

If we need JetPack/XMLRPC:

sudo apt install php-xmlrpc

You now have your PHP components installed, but you need to make a slight configuration change to make your setup more secure.

sudo vim /etc/php/7.2/fpm/php.ini

In this file, find the parameter that sets cgi.fix\_pathinfo. This will be commented out with a semicolon (;) and set to “1” by default.

cgi.fix_pathinfo=0

This is an extremely insecure setting because it tells PHP to attempt to execute the closest file it can find if the requested PHP file cannot be found. This could allow users to craft PHP requests in a way that would allow them to execute scripts that they shouldn’t be allowed to execute.

Save and close the file when you are finished. Then, restart your PHP processor by:

sudo systemctl restart php7.2-fpm

This will implement the change that you have made.

Configuring Nginx to Use the PHP Process

Presently, you have all of the required components installed. The only configuration change you still need to make is to tell Nginx to use the PHP processor for dynamic content.

This is done on the server block level (server blocks are similar to Apache’s virtual hosts). Open the default Nginx server block configuration file

sudo vim /etc/nginx/sites-available/default

NGINX Config:

Currently, with the comments removed, the Nginx default server block file looks like this: https://github.com/arbitrarily/nginx-template/blob/master/nginx

Replace the NGINX config only once the site has been set up initially. So in this step, we only need to update:

set $base /var/www/#####;
root $base;

# Add index.php to the list if you are using PHP
index index.php index.html index.htm index.nginx-debian.html;

server_name _ #####;

location / {
  try_files $uri $uri/ /index.php?q=$uri&$args;
}

# fastcgi
location ~ \.php$ {
  # fastcgi
  fastcgi_pass                unix:/run/php/php7.2-fpm.sock;
  fastcgi_split_path_info     ^(.+\.php)(/.+)$;
  fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param               PHP_ADMIN_VALUE open_basedir=$base/:/usr/lib/php/:/tmp/;
  fastcgi_intercept_errors    off;

  fastcgi_buffer_size             128k;
  fastcgi_buffers                 256 16k;
  fastcgi_busy_buffers_size       256k;
  fastcgi_temp_file_write_size    256k;

  # default fastcgi_params
  include fastcgi_params;
  include snippets/fastcgi-php.conf;

}

Again, save and close the file. Then, test your configuration file for syntax errors by typing:

sudo nginx -t

Prevent Access to NGINX Version Number

Further security reading: (https://www.thefanclub.co.za/how-to/how-secure-ubuntu-1604-lts-server-part-1-basics)

The server signature of Nginx is a header indicating the version of the webserver. It is configured by default so, anyone can read the header and knows you’re running Nginx, with the exact version. It is of course an information you don’t want to to share.

sudo apt install -y nginx-extras
sudo vim /etc/nginx/nginx.conf

Set:

more_set_headers "Server: Webserver";
server_tokens off;

We’re gonna have to increase the the following values by pasting:

server_names_hash_bucket_size 64;
variables_hash_max_size 64;
server_names_hash_max_size 4096;
variables_hash_bucket_size 4096;

If any errors are reported, go back and recheck your file before continuing. When you are ready, reload Nginx to make the necessary changes:

sudo systemctl reload nginx

Hardening PHP

Further reading: (https://www.thefanclub.co.za/how-to/how-secure-ubuntu-1604-lts-server-part-1-basics) Open:

sudo vim /etc/php/7.2/fpm/php.ini

Add or edit the following lines then save:

disable_functions = exec,system,shell_exec,passthru
register_globals = Off
expose_php = Off
display_errors = Off
track_errors = Off
html_errors = Off
magic_quotes_gpc = Off
mail.add_x_header = Off
session.name = NEWSESSID

Security

Install Fail2ban

Any server exposed to the public can become a potential target to hackers. They can try to do all kinds of attacks on your server and the only way to make their attempts futile is to install Fail2Ban. To install Fail2ban run:

sudo apt-get install fail2ban

Although the default settings for Fail2ban may work depending on your server needs, you may edit Fail2Ban configuration file. The .conf files are read first followed by .local files hence you should make changes to .local files and leave .conf files untouched.

To edit the jail.conf file copy it to jail.local using the command below:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Then edit the new file by typing:

vim /etc/fail2ban/jail.local

On this file, you can set the ban time, max retries, ban find time etc depending on the level of security that you need.

Secure Shared Memory

Shared memory can be used in an attack against a running service, apache2 or httpd for example. (https://www.owasp.org/index.php/PHP_Configuration_Cheat_Sheet)

sudo vim /etc/fstab

add:

tmpfs	/run/shm	tmpfs	ro,noexec,nosuid	0 0

Check for rootkits - RKHunter and CHKRootKit

sudo apt-get install rkhunter chkrootkit

To run chkrootkit open a terminal window and enter:

sudo chkrootkit

To update and run RKHunter:

sudo rkhunter --update
sudo rkhunter --propupd
sudo rkhunter --check

This where we install the repo in /var/www

Install Site

Just a few more things to get the site up and running. Create the directory first:

cd /var/www

sudo mkdir @@@
sudo chown $(whoami) @@@

Clone the project (we use BitBucket for work projects typically):

git clone git@bitbucket.org:##########/@@@.git @@@

Then give the server access to wp-content.

cd /var/www/@@@
sudo chown -R www-data wp-content

Update WP-Config

Visit the site now: http://###IP_ADDRESS###/wp-admin/setup-config.php

Configure the setup wizard with the database info and copy the generated wp-config into a new file in /var/www/@@@/wp-config.php; Near the end, around where define('WP_DEBUG', false); is, just drop this in too:

define('DISALLOW_FILE_EDIT', true);

Also, don’t forget to create a Git user and add the git keys to Github:

git config --global user.email "xxxx@xxxxxxxx.com"
git config --global user.name "XXXXXXXX"
git config --global core.editor "vim"

Click Run the installation. Enter in the website’s info and you’re done.

ender