How to setup LEMP stack on Debian 9 Stretch Linux


Obtaining a working LEMP stack (Linux, nginx, mariadb, php) on Debian 9 Stretch

Operating System and Software Versions

  • Operating System: – Debian 9 Stretch


Root access on a working Debian 9 Stretch installation




  • # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
  • $ – requires given linux commands to be executed as a regular non-privileged user


Following this simple how-to you will be able to install the LEMP stack on Debian 9 (Stretch). We will follow the ‘letter by letter approach’, obviously skipping the ‘L’ that is for Linux: having a working Debian 9 installation, you already have fulfilled this requirement.

I’m going to start from a barebone Debian 9 setup, using apt-get to install the needed packages. Obviously it’s perfectly fine to use aptitude instead.

The ‘E’ part of the stack: nginx

What is nginx? Nginx, like apache, is an http server. Compared to the latter, it is considered to be more lightweight. While apache has the ability to process many interpreted languages ‘directly’, the nginx focus is on static contents, delivering the management of dynamic ones on separate software.

Let’s refresh the repositories and install nginx on our Debian machine. We run:

# apt-get update && apt-get install nginx

Few seconds later nginx will be installed. The next step is to start the service:

# systemctl start nginx

You may want to enable the service to be automatically started at boot time:

# systemctl enable nginx

If you installed the web server on the same machine used as a client, to verify that it is working, you should simply point the browser to localhost, otherwise you have to use the server machine specific ip address.

Since I’m running Debian on a kvm virtual machine, I had to point the browser to the server ip. If you don’t know what the server ip is, you can simply find it by using the ip or ifconfig commands (the latter is now considered deprecated, nonetheless it does its job well). Using ip you would run:

# ip address show

The above command will give an output similar to the following:

$ su -c "ip address show"
1: lo: <loopback,up,lower_up> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens3: <broadcast,multicast,up,lower_up> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:1b:80:28 brd ff:ff:ff:ff:ff:ff
    inet brd scope global ens3
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe1b:8028/64 scope link 
       valid_lft forever preferred_lft forever

The address is To reach the server from outside you will also need to configure the firewall to allow incoming traffic on port 80. For example, if you are using firewalld, you could just add the http service to the proper zone (the ‘public’ zone by default):

# firewall-cmd --zone=public --add-service=http

You may also want to add the --permanent option to the command above, to make the change persistent.
Let’s point the browser to the server address, and see what happens:

That’s it! The nginx welcome page shows us that the web server has been successfully installed and it’s working correctly.

Now, the database: M is for MariaDB

In almost all major distributions mysql has been ditched in favor of MariaDB, a fully compatible and more feature-loaded fork, created when mysql was acquired by Oracle:

# apt-get install mariadb-server mariadb-client

This command will install both the mariadb-server and the mariadb-client packages (along with all needed dependencies). The mariadb-client package contains the utilities needed to communicate with the server. The mariadb.service unit will be automatically started, and at this point you should already have a running mariadb. However, we are not done yet: to setup the mariadb root password and to tune some settings you have to run the following script:

# mysql_secure_installation

It will guide you to a series of steps to put mariadb in a consistent state.

The ‘P’ is for PHP

The default php version on Debian stretch is 7.0: we need to install the following packages:

# apt-get install php-fpm php-mysql

The php7.0-fpm daemon will be automatically started. Like we said before, nginx relies on external software to manage dynamic contents, and php-fpm is the FastCGI Process Manager to which nginx will redirect the php requests. To tune nginx to work with php-fpm, we must edit the default site configuration.

Debian keeps the configuration for each site (‘server-blocks’ in the nginx terminology – sort of the equivalent of apache VirtualHosts) in two folders:
/etc/nginx/sites-available and /etc/nginx/sites-enabled. In the former directory we have the configurations that get symlinked to the latter one when a site is enabled. The default site configuration is therefore reachable at /etc/nginx/sites-available/default. Let’s edit the file:

	  # pass PHP scripts to FastCGI server
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                # With php-fpm (or other unix sockets):
                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass;

Edit the relevant part of the file so that it reflects the above configuration. By removing the comments in the lines above, we are basically telling nginx that we want to use php-fpm, and to use the related unix socket.

Now we have to test the configuration with a simple php script, but first we have to restart nginx for the changes we made to be effective:

# systemctl restart nginx

The document root directory for the default server-block in Debian is /var/www/html: we will create a simple php script in there to display some information and to verify that everything works correctly:

# echo "<?php phpinfo(); ?>" > /var/www/html/infopage.php

To verify that the script works, navigate with your browser to its location. In my case it is