Installing the LEMP Stack on Ubuntu 16.04
Most of the WordPress, Nextcloud and custom PHP sites I look after run on LEMP: Linux, nginx (the E is for "engine-x"), MySQL and PHP. I've rebuilt this stack from memory so many times that I finally wrote it down. Ubuntu 16.04's own repository is recent enough (nginx 1.10, MySQL 5.7, PHP 7.0), so no third-party sources are needed.
Below is a clean 16.04 server with PHP-FPM talking to nginx over a Unix socket. That's what I use on a single server. You need a sudo user and ufw allowing SSH; a hostname helps but isn't required.
nginx first
sudo apt-get update
sudo apt-get install -y nginx
sudo ufw allow 'Nginx Full'
systemctl status nginx
Browse to the server's IP address; you should see the "Welcome to nginx!" page.
MySQL 5.7
sudo apt-get install -y mysql-server
The installer asks for a root password. Set one, then run the hardening script, which removes the anonymous user and test database and disables remote root login:
sudo mysql_secure_installation
I answer yes to everything except the VALIDATE PASSWORD PLUGIN question, since I'd rather pick my own passwords than fight a policy. Confirm you can log in:
mysql -u root -p -e 'SELECT VERSION();'
On 16.04 you should see 5.7.x-0ubuntu0.16.04.x.
PHP 7.0 with FPM
Don't install the php metapackage; it pulls in Apache, which I found out after wondering why port 80 was busy. Install FPM and the extensions most applications need explicitly:
sudo apt-get install -y php7.0-fpm php7.0-mysql php7.0-curl php7.0-gd \
php7.0-mbstring php7.0-xml php7.0-zip php7.0-json php7.0-opcache
One security setting in /etc/php/7.0/fpm/php.ini is worth changing right away. Find cgi.fix_pathinfo, uncomment it and set it to 0 so that PHP will not try to execute the closest matching file when a request points at a non-existent script:
cgi.fix_pathinfo=0
Then restart FPM:
sudo systemctl restart php7.0-fpm
FPM listens on /run/php/php7.0-fpm.sock by default, which is what the nginx config below refers to.
Wiring nginx to FPM
Create a site file and disable the default:
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
The snippets/fastcgi-php.conf file shipped with Ubuntu's nginx sets SCRIPT_FILENAME and the other FastCGI parameters, so you do not need to repeat them. Enable the site:
sudo mkdir -p /var/www/example.com/public_html
sudo chown -R $USER:$USER /var/www/example.com
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
A database for the app
Applications shouldn't connect as root. Make a dedicated database and user:
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
Does PHP actually work?
Drop a test script into the document root:
echo '<?php phpinfo();' > /var/www/example.com/public_html/info.php
Load http://example.com/info.php. The page should report Server API: FPM/FastCGI and list mysqlnd and pdo_mysql under loaded modules. Then check the database connection from PHP:
php -r '$m = new mysqli("localhost","appuser","a-long-random-password","appdb"); echo $m->server_info, PHP_EOL;'
Delete info.php when you are done; it discloses far too much about the server to leave lying around.
When it goes wrong
- Browser downloads the
.phpfile instead of running it. Thelocation ~ \.php$block is missing or the site was not reloaded.nginx -T | grep fastcgi_passshows whether the directive is active. 502 Bad Gatewaywithconnect() to unix:/run/php/php7.0-fpm.sock failed (2: No such file or directory)in/var/log/nginx/error.log. PHP-FPM is not running, or a different socket path is set in/etc/php/7.0/fpm/pool.d/www.confunderlisten. Start FPM or align the paths.ERROR 1698 (28000): Access denied for user 'root'@'localhost'. On Ubuntu's MySQL 5.7, root uses theauth_socketplugin when no password was set during install, so you must usesudo mysqlrather thanmysql -u root -p. This is by design, not a bug.File not found.from PHP on every request. Therootdirective sits inside alocationblock instead of theserverblock, so FPM receives the wrongSCRIPT_FILENAME.
Next I'd add a Let's Encrypt certificate, then look at pm.max_children in the FPM pool so it matches the RAM you actually have.