Setup A LEMP Web Server On Ubuntu Or Debian

By:   –  Last updated:   –  #ubuntu ·  #nginx ·  #web ·  #linux ·  #php

Code Theme [Dark]

Content Overview [Hide]

1 Install Ubuntu/Debian

It is highly recommended to install stable Debian release, or Ubuntu LTS (Long Term Support) version. After installation running apt-get update command to keep software packages list in the server up to date.

[[email protected] ~] apt-get update

2 Add A Non-root User

A best practice and good habit is using a standard user instead of using root everywhere.

[[email protected] ~] adduser NEWUSER
[[email protected] ~] passwd NEWUSER

Install Vim and sudo:

[[email protected] ~] apt-get install vim
[[email protected] ~] apt-get install sudo

Add standard non-root user to sudo list:

[[email protected] ~] visudo
# add a new line

3 Secure Server With Firewall

Setup iptables firewall to secure server.

First create iptables rules file:

[[email protected] ~] sudo vim /etc/firewall.rules

Add rules to allow only necessary and essential traffic. For example, SSH connection on port 22, Http or https listen port 80 and 443.


#  Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
#  Accept all established inbound connections

#  Allow all outbound traffic - you can modify this to only allow certain traffic

#  Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

#  Allow SSH connections
#  The -dport number should be the same port number you set in sshd_config
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

#  Allow ping
-A INPUT -p icmp -j ACCEPT

#  Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

#  Drop all other inbound - default deny unless explicitly allowed policy


If you also use SSH alternative: MOSH , add a new line for 'MOSH':

# Allow MOSH
-A INPUT -p udp --dport 60000:61000 -j ACCEPT

The new rules file is ready, now tell iptables to load the rules file:

[[email protected] ~] sudo iptables-restore < /etc/firewall.rules

Double check the rules using command:

[[email protected] ~] sudo iptables -L

Write a script to read the rules automatically every time the server is up:

[[email protected] ~] sudo vim /etc/network/if-pre-up.d/firewall

Script content is:

/sbin/iptables-restore < /etc/firewall.rules

Make it executable:

[[email protected] ~] sudo chmod +x /etc/network/if-pre-up.d/firewall

4 Install MySQL

Install MysSQL by apt:

[[email protected] ~] sudo apt-get install mysql-server php5-mysql

After installation, initialze MySQL with command:

[[email protected] ~] sudo mysql_install_db

Then run the secure setup script:

[[email protected] ~] sudo /usr/bin/mysql_secure_installation

follow the prompt message to change root password, disable anonymous user, remove test database and so on.

5 Install PHP

Install php5 and php-fpm:

[[email protected] ~] sudo apt-get install php5 php5-fpm

Configure PHP:

[[email protected] ~] sudo vim /etc/php5/fpm/php.ini

Search cgi.fix_pathinfo, set ites value to 0.

Save and restart the php-fpm service:

[[email protected] ~] sudo service php5-fpm restart

6 Nginx

Install it by apt:

[[email protected] ~] sudo apt-get install nginx

Create a new website confiure according to the sample configure: /etc/nginx/sites-available/default.

[[email protected] ~] sudo vim /etc/nginx/sites-available/default

Change the directive in Server block:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/;
    index index.php index.html index.htm;


    error_log /var/www/;
    access_log /var/www/;

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

    error_page 404 /404.html;

    location = /50x.html {
            root /usr/share/nginx/html;

    location ~ \.php {
            #fastcgi_split_path_info ^(.+\.php)(/.+)$;
            # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

            # With php5-fpm:
            fastcgi_pass unix:/var/run/php5-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;

    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            expires 30d;
            log_not_found off;
            access_log off;

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
            deny all;

If the website uses WordPress, to enable pretty permalinks of WordPress, change the / location block to:

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

If the website routing uses PATH_INFO, add configurations to php location block:

location ~ \.php {
        #fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

        # With php5-fpm:
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;

        set $path_info "";
        set $real_script_name $fastcgi_script_name;
        if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
                set $real_script_name $1;
                set $path_info $2;

        fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
        fastcgi_param SCRIPT_NAME $real_script_name;
        fastcgi_param PATH_INFO $path_info;

Restart or reload nginx:

[[email protected] ~] sudo service nginx restart
[[email protected] ~] sudo service nginx reload

7 Install Fail2Ban.


[[email protected] ~] sudo apt-get install fail2ban

Create a custom local jail configuration file to override default fail2ban configurations, if neccessay.

[[email protected] ~] sudo nano /etc/fail2ban/jail.local

Add new configuration values you want to override in jail.local:

bantime = 3600
findtime = 300
maxretry = 3

Above settings mean that if there are 3 times failing tries (e.g. SSH) in 300 seconds has been detected, the request client IP will be banned for 3600 seconds.