How to Install Nginx with PHP + MySQL (LEMP) on Debian 9
This tutorial will show you the installation of the Nginx web server on Debian 9 (Stretch). Nginx (pronounced “engine x”) is a free, open-source, high-performance HTTP server. Nginx is known for its stability, rich feature set, simple configuration, and low resource consumption. This tutorial shows the installation of Nginx with PHP support (through PHP-FPM) and MySQL and MariaDB. This setup is often referred to as LEMP = Linux + nginx (pronounced “engine x”) + MySQL + PHP) .
Preliminary Note
In this tutorial, I use the hostname server1.example.com with the IP address 192.168.1.100. These settings might differ for you, so you have to replace them where appropriate. You should have a Debian 9 server, I will use the Debian minimal server as the base system for this tutorial.
Update the Debian System
It is recommended to update the package lists and install any pending updates before we start with the Nginx setup. Run the following commands to install any pending updates.
apt-get update
apt-get upgrade -y
I will use the nano editor later to edit configuration files. Nano can be installed with this command:
apt-get -y install nano
Install Nginx
Nginx is available as a package for Debian 9 which we can be installed with this command:
apt-get -y install nginx
Now start Nginx web server:
systemctl start nginx.service
Type in your web server’s IP address or hostname into a browser (e.g. http://192.168.1.100), and you will see the following page:
The default nginx document root on Debian Linux is /var/www/html.
Install MySQL or MariaDB
In this step, I will show you how to install MySQL or MariaDB. You are free to choose which database system you prefer. Just ensure that you install only one database engine and not MySQL and MariaDB together.
Install MySQL
The MySQL packages for Debian 9 can be obtained from Oracle directly. Oracle provides a MySQL repository package which integrates the Oracle MySQL repository into Debian so that we can install and update MySQL with apt. Get the MySQL apt repository package here in case the wget download below fails due to changes in the download URL.
cd /tmp
wget https://dev.mysql.com/get/mysql-apt-config_0.8.9-1_all.deb
dpkg -i mysql-apt-config_0.8.9-1_all.deb
Choose to configure ‘OK’ in the list, then focus the OK button at the footer and press enter. This will select the current stable version, which is MySQL 5.7.
Now we’ll update the package list and install the MySQL server and client package.
apt-get update
apt-get -y install mysql-community-client mysql-community-server
The MySQL installer will ask you to set a password for the MySQL root user. Choose a long and secure password as this pasword allows full administrative access to the MySQL database.
Re-enter the password as requested.
Install MariaDB
In order to install MariaDB, we run:
apt-get -y install mariadb-server mariadb-client
Unlike the MySQL installer, the MariaDB installer will not set a root password during installation. To secure the MariaDB installation, remove the anonymous user and to disable the test database, run this command:
mysql_secure_installation
Answer the questions as follows:
Change the root password? [Y/n] <– y
New password: <– Enter a new MySQL root password
Re-enter new password: <– Repeat the MySQL root password
Remove anonymous users? [Y/n] <– y
Disallow root login remotely? [Y/n] <– y
Remove test database and access to it? [Y/n] <– y
Reload privilege tables now? [Y/n] <– y
Installing PHP
We can make PHP work in nginx through PHP-FPM (PHP FastCGI Process Manager). This is an alternative PHP FastCGI implementation with some additional features useful for sites of any size, especially busier sites. Install PHP 7 as follows:
apt-get -y install php7.0-fpm
PHP-FPM is a daemon process (with the systemd unit file php7.0-fpm.service) that runs a FastCGI server on the socket /var/run/php/php7.0-fpm.sock.
Configuring Nginx
The Nginx configuration is in /etc/nginx/nginx.conf which we open now:
nano /etc/nginx/nginx.conf
The configuration is easy to understand (you can learn more about it here: https://www.nginx.com/resources/wiki/start/topics/examples/full/ and here: https://www.nginx.com/resources/wiki/)
First set the keepalive_timeout to a reasonable value like 2 seconds:
[...]
keepalive_timeout 2;
[...]
The virtual hosts are defined in server {} containers. The default vhost is defined in the file /etc/nginx/sites-available/default – let’s modify it as follows:
nano /etc/nginx/sites-available/default
[...] server {
listen 80 default_server;
listen [::]:80 default_server;# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
#
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;root /var/www/html;
# 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 / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}# 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 127.0.0.1:9000;
}# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
} [...]
server_name _; makes this a default catchall vhost (of course, you can as well specify a hostname here like www.example.com).
I’ve added index.php to the index line. root /var/www/html; means that the document root is the directory /var/www/html.
The important part for PHP is the location ~ \.php$ {} stanza. Uncomment it like shown above to enable it. There are two fastcgi_pass lines included, uncomment just the one for the php-7.0-fpm.sock file.
Now save the file and reload Nginx:
systemctl reload nginx.service
Next open /etc/php/7.0/fpm/php.ini…
nano /etc/php/7.0/fpm/php.ini
… and set cgi.fix_pathinfo=0:
[...] ; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's ; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok ; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting ; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting ; of zero causes PHP to behave as before. Default is 1. You should fix your scripts ; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. ; http://php.net/cgi.fix-pathinfo cgi.fix_pathinfo=0 [...]
… then you might want to rise the POST limit and the file upload limit:
post_max_size = 25M
upload_max_filesize = 20M
When you plan to upload large files by PHP then you should raise the values to even 500M or more. M stands here for Megabytes.
Finally, you might want to set the timezone to your local timezone. In my case, the timezone is:
date.timezone = 'Europe/Berlin'
You can find the list of supported timezones here.
Reload PHP-FPM to apply the changes:
systemctl reload php7.0-fpm.service
Now create the following PHP file in the document root /var/www/html/:
nano /var/www/html/info.php
<?php phpinfo();
Now we call that file in a browser (e.g. http://192.168.1.100/info.php):
As you see, PHP 7 is working, and it’s working through FPM/FastCGI, as shown in the Server API line. If you scroll further down, you will see all modules that are already enabled in PHP. MySQL is not listed there which means we don’t have MariaDB / MySQL support in PHP yet.
Getting MySQL / MariaDB Support in PHP
To get MySQL support in PHP, we can install the php7.0-mysqlnd package. It’s a good idea to install some other PHP modules as well as you might need them for your applications. You can search for available PHP modules like this:
apt-cache search php7.0
Pick the ones you need and install them like this:
apt-get -y install php7.0-mysqlnd php7.0-curl php7.0-gd php7.0-intl php-pear php-imagick php7.0-imap php7.0-mcrypt php-memcache php7.0-intl php7.0-pspell php7.0-recode php7.0-sqlite3 php7.0-tidy php7.0-xmlrpc php7.0-xsl
Now reload PHP-FPM:
systemctl reload php7.0-fpm.service
Now reload http://192.168.1.100/info.php in your browser and scroll down to the modules section again. You should now find lots of new modules there, including the MySQLi and MySQLnd module:
Making PHP-FPM use a TCP connection (optional)
By default, PHP-FPM is listening on the socket /var/run/php/php7.0-fpm.sock and that’s the recommended and fastest way to connect PHP to Nginx. However, there might be setups where you want to let Nginx connect to PHP over the network. It is possible to make PHP-FPM use a TCP connection. To do this, open /etc/php/7.0/fpm/pool.d/www.conf…
nano /etc/php/7.0/fpm/pool.d/www.conf
… and make the listen line look as follows:
[...] ;listen = /var/run/php5-fpm.sock listen = 127.0.0.1:9000 [...]
This will make PHP-FPM listen on port 9000 on the IP 127.0.0.1 (localhost). Make sure you use a port that is not in use on your system.
Then reload PHP-FPM:
systemctl reload php7.0-fpm.service
Next go through your Nginx configuration and all your vhosts and change the line fastcgi_pass unix:/var/run/php7.0-fpm.sock; to fastcgi_pass 127.0.0.1:9000;, e.g. like this:
nano /etc/nginx/sites-available/default
[...]
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 127.0.0.1:9000;
} [...]
Finally, reload Nginx:
systemctl reload nginx.service
Enable SSL and HTTP/2 in Nginx
Most websites today use SSL (TLS) to provide secure access. In this chapter, I’ll show you how to create an SSL certificate an how to activate SSL in Nginx. You can either use a self-signed SSL certificate or request an officially signed SSL certificate from Let’s encrypt. Let#s encrypt SSL certificates are available free of charge, but you must have a valid domain name that points to your server in DNS already. If you have no domain name yet or when your server is on a local network and not accessible from outside, then use a self-signed SSL certificate. Follow either the steps for a self-signed SSL certificate or the Let’s encrypt certificate below but not both.
Create a self-signed SSL certificate
Create the SSL key file with the OpenSSL command:
openssl genrsa -out /etc/ssl/private/nginx.key 4096
Then create the self-signed SSL certificate:
openssl req -new -x509 -key /etc/ssl/private/nginx.key -out /etc/ssl/certs/nginx.pem -days 3650
The command will ask for details like country, state, city, company name and domain name.
Activate the self-signed SSL certificate in Nginx. To do so, edit the nginx.conf file again:
nano /etc/nginx/sites-available/default
and make the server part it look like this:
server {
listen 80 default_server;
listen [::]:80 default_server;# SSL configuration
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;ssl on;
ssl_certificate_key /etc/ssl/private/nginx.key;
ssl_certificate /etc/ssl/certs/nginx.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;root /var/www/html;
# 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 / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}# 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 127.0.0.1:9000;
}# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
Restart Nginx to apply the changes.
systemctl restart nginx.service
Now open the https URL of your server in a browser, e.g. https://192.169.1.100/. You will get a security warning which you’ll have to accept to proceed. Afterwards, you will see the Nginx start page, the warning icon in the URL bar of the browser indicates that we use a self-signed SSL certificate.
Use a free Let’s Encrypt SSL certificate
In this chapter, I’ll describe how to secure your Nginx server by using a free Let’s encrypt SSL certificate. A prerequisite is that you own a domain name which points to the IP of the server where you install Nginx on at the moment.
Install Certbot, the Let’s encrypt client that is used to obtain a free SSL certificate.
apt-get -y install certbot python-certbot-nginx
Edit the website configuration file /etc/nginx/sites-available/default and set your domain name(s) in the server_name line:
nano /etc/nginx/sites-available/default
The line should look like this after editing:
server_name example.com;
Replace example.com with your own domain name. If you have multiple domain names or subdomains, then add them separated by whitespace.
server_name example.com www.example.com otherdomain.tld;
Now we request an SSL certificate from let’s encrypt by using the nginx plugin from Certbot.
certbot –nginx -d example.com
Multiple domains can be added by repeating the -d option. Example:
certbot certonly –webroot -d example.com -d www.example.com
Certbot will ask you for an email address where renewal notifications shall be sent to. Enter a valid email address here:
Enter email address (used for urgent renewal and security notices) (Enter ‘c’ to cancel): [email protected]
Accept the license terms by entering ‘A’.
——————————————————————————-
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
——————————————————————————-
(A)gree/(C)ancel: A
Here the further dialog that requests the SSL certificate. I’ve added my answers in red.
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
http-01 challenge for www.example.com
Select the webroot for example.com:
——————————————————————————-
1: Enter a new webroot
——————————————————————————-
Press 1 [enter] to confirm the selection (press ‘c’ to cancel): 1
Input the webroot for example.com: (Enter ‘c’ to cancel):/var/www/html
Select the webroot for www.example.com:
——————————————————————————-
1: Enter a new webroot
2: /var/www/html
——————————————————————————-
Select the appropriate number [1-2] then [enter] (press ‘c’ to cancel): 2
Waiting for verification…
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
IMPORTANT NOTES:
– Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/example.com/fullchain.pem. Your cert will
expire on 2018-04-23. To obtain a new or tweaked version of this
certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run “certbot
renew”
– If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
There is an alternate method by using the ‘–nginx’ option in Certbot, but this method does not seem to work at the moment due to the TLS-SNI-01 problem in Let’s encrypt. The webroot method, as shown above, works fine though.
The newly generated SSL certificate is in a subfolder of /etc/letsencrypt/live/ folder. The exact path is shown in the Certbot output.
Now we’ll add this SSL certificate in our Nginx website file. Edit the Nginx default file:
nano /etc/nginx/sites-available/default
and change the SSL section like this:
[...] server {
listen 80 default_server;
listen [::]:80 default_server;# SSL configuration
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;ssl on;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
#
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;root /var/www/html;
# 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 / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}# 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 127.0.0.1:9000;
}# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
} [...]
Replace ‘example.com’ in the SSL certificate path with your own domain name. Restart Nginx to apply the changes.
systemctl restart nginx.service
Virtual Machine image
This tutorial is available as ready to use virtual machine in OVA / OVF format for Howtoforge subscribers. The VM format is compatible with VMWare and Virtualbox and probably some other tools that can import this format. You can find the download link in the right menu on the top. Click on the file name to start the download.
The login details of the VM are:
SSH Login
Username: administrator
Password: howtoforge
Run ‘su’ to become root user, the root password is ‘howtoforge’ as well.
The virtual machine image uses MySQL as the database server.
MySQL Login
Username: root
Password: howtoforge
Please change the passwords after the first boot.
The VM is configured for the static IP 192.168.1.100, the IP can be changed in the file /etc/network/interfaces.
Links
Share this page: