This guide walks through deploying WordPress on Ubuntu 18.04 with a LEMP stack, TLS via Let's Encrypt, and a baseline set of production controls.
Operational context
- When to use this: you need a self-managed WordPress instance with Nginx, database isolation, HTTPS, and firewall rules.
- What it delivers: a working LEMP deployment with certificate automation and sensible defaults for a first production cut.
- Tradeoff: this reflects Ubuntu 18.04-era package choices. Treat the commands as historical unless you are actually maintaining Ubuntu 18.04.
Still valid on newer Ubuntu versions?
The deployment shape is still useful: non-root SSH access, firewall rules, Nginx, PHP-FPM, a dedicated database user, WordPress salts, TLS, and post-install verification. The exact packages have changed.
Before reusing this on a newer Ubuntu release, check:
- PHP package names and supported PHP version (
php-fpm,php-mysql, and related extensions) - Certbot installation method; many current systems use Snap or distribution packages rather than the old PPA
- MySQL versus MariaDB defaults, authentication plugins, and
utf8mb4support - whether your hosting provider already enables UFW, cloud firewalls, or managed TLS
- backup and restore process before applying changes to an existing site
In particular, do not copy the package list blindly on current Ubuntu releases. php-mcrypt is obsolete and removed from modern PHP stacks, MySQL 8 commonly requires CREATE USER before GRANT, and Certbot PPA instructions have largely been replaced by distribution packages or Snap.
Index
- WordPress on LEMP with Let's Encrypt
- Installation steps
- Step 1: Create a sudo user
- Step 2: Set up the firewall
- Step 3: Install Nginx web server
- Step 4: Install MySQL database server
- Step 5: Install PHP
- Step 6: Configure Nginx to serve PHP
- Step 7: Download and configure WordPress
- Step 8: Obtain and configure the SSL Certificate with Let's Encrypt
- Step 9: Finalize the WordPress installation
WordPress on LEMP with Let's Encrypt
What is LEMP?
This is an acronym that describes a Linux operating system, with an Nginx (pronounced like “Engine-X”) web server, a MySQL / MariaDB database server and the dynamic processing is handled by PHP.
What is Let's Encrypt?
Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free TLS/SSL certificates, thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps.
Prerequisites
- A server (I run my servers on DigitalOcean *)
- Ubuntu 18.04 installed
- SSH access
* Signing up to DigitalOcean via that link, you receive a $50, 30-day credit as soon as you add a valid payment method to your account.
Installation steps
Step 1: Create a sudo user
Since we will be completing the steps in this guide using a non-root user with sudo privileges, we need to create a user for that.
Skip this step if you already have it
# Login as root
$ ssh root@your_server_ip
# Create a new user
$ adduser santa
# Set up sudo privileges
$ usermod -aG sudo santa
Highly recommended: configure SSH public key authentication and disable password login. You can follow the process in this Linuxize article.
Before you log out from your root session, you should test your new user login in a new terminal, on your local machine
# Login
$ ssh santa@your_server_ip
# Test sudo abilities
$ sudo ufw app list
Step 2: Set up the firewall
Ubuntu 18.04 ships with UFW enabled by default. Restrict inbound access to the services you actually need.
# Allow access on SSH port
$ sudo ufw allow 22
# Allow access on HTTP port for initial site access and Let's Encrypt HTTP-01 validation
$ sudo ufw allow 80
# Allow access on HTTPS port
$ sudo ufw allow 443
# Enable the firewall
$ sudo ufw enable
# Check firewall status
$ sudo ufw status
Step 3: Install Nginx web server
Installing Nginx is straightforward:
# Install Nginx
$ sudo apt-get update && sudo apt-get install nginx
Then open a browser tab, enter your server IP address, and you should see the default Nginx welcome page.
Step 4: Install MySQL database server
Here we're going to use MySQL, but alternatively you could also use MariaDB. More about it on this Linuxize article
# Install MySQL
$ sudo apt-get install mysql-server
# Configure the Installation
$ mysql_secure_installation
You’ll be prompted to answer some questions. We recommend you answer as follows:
NOTE: Make sure you remember your MySQL password, as you’ll be using it later on.
Enter current password for root (enter for none): Just press the Enter
Set root password? [Y/n]: Y
New password: Enter password
Re-enter new password: Repeat 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
Next, create the database, user, and password for WordPress.
# Login to your database server
$ mysql -u root -p
# Create a new database for WordPress
mysql> CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# Create a separate MySQL user account that we will use exclusively to operate on our new database
# wordpressuser and password are placeholders; replace them with site-specific credentials
mysql> GRANT ALL ON wordpress.* TO 'wordpressuser'@'localhost' IDENTIFIED BY 'password';
# Flush the privileges so that the current instance of MySQL knows about the recent changes we’ve made
mysql> FLUSH PRIVILEGES;
# Exit out of MySQL console
mysql> EXIT;
On MySQL 8 and newer, create the user explicitly before granting permissions:
CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpressuser'@'localhost';
FLUSH PRIVILEGES;
Step 5: Install PHP
In this step we'll install the PHP extensions required for WordPress to run and for Nginx to process PHP (php-fpm).
# Install PHP packages
$ sudo apt-get install php-fpm php-mysql php-curl php-gd php-mbstring php-mcrypt php-xml php-xmlrpc php-intl php-zip
# Find out which PHP 7 version you have
$ php -v | head -1
php-mcrypt was already deprecated around PHP 7.2 and is not available on modern Ubuntu/PHP combinations. If your package manager cannot find it, remove it from the install command unless a legacy application specifically requires it.
We need to know which PHP 7 version, because next step is to configure the PHP Processor. Replace 7.2 with your version... i.e.: 7.0
# Open the main php-fpm pool configuration file
$ sudo nano /etc/php/7.2/fpm/pool.d/www.conf
In there you are to add or uncomment the line that contains the following: security.limit_extensions
ending up with the following value: security.limit_extensions = .php
You can find more about the above, on this Server Fault thread
# Configure the PHP processor
$ sudo nano /etc/php/7.2/fpm/php.ini
And adjust the following values:
memory_limit = 256M
upload_max_filesize = 10M
cgi.fix_pathinfo = 0
max_execution_time = 360
date.timezone = Europe/Berlin
And finally
# Restart PHP
$ service php7.2-fpm restart
Step 6: Configure Nginx to serve PHP
We need a few minor adjustments to our Nginx server block files to be able to serve PHP / WordPress.
Replace your_domain.com with your actual domain.
# Create a few directories required for Nginx configuration
$ sudo mkdir /etc/nginx/sites-available && sudo mkdir /etc/nginx/sites-enabled
# Create your virtual host
$ sudo nano /etc/nginx/sites-available/your_domain.com
On Ubuntu's packaged Nginx, /etc/nginx/sites-available and /etc/nginx/sites-enabled usually already exist. Create them only if your install does not provide them.
This set of instructions will tell Nginx to listen for requests to your_domain.com
and serve your WordPress site.
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /var/www/html;
index index.php index.html index.htm;
server_name your_domain.com;
client_max_body_size 10M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
include /etc/nginx/snippets/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Disable logging for these static requests and will mark them as
# highly cacheable since these are typically expensive resources to serve
location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { log_not_found off; access_log off; allow all; }
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
# SECURITY : Deny all attempts to access PHP Files in the uploads directory
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
# Deny access to .ht* files
location ~ /\.ht {
deny all;
}
}
Use default_server only for the server block that should catch unmatched hostnames. If this host will run multiple sites, remove default_server from additional vhosts to avoid conflicts.
On newer Nginx versions, replace expires max with an explicit long-lived cache value, such as expires 30d;, when that is appropriate for your asset pipeline.
Last thing remaining is to enable the site and restart Nginx
# Create a symlink to enable your virtual host
$ sudo ln -s /etc/nginx/sites-available/your_domain.com /etc/nginx/sites-enabled/your_domain.com
# Restart nginx server
$ sudo service nginx restart
Step 7: Download and configure WordPress
# Download latest WordPress version
$ cd /tmp && wget https://wordpress.org/latest.tar.gz
# Unpack it
$ tar -zxvf latest.tar.gz
# Move it from tmp to /var/www
$ sudo rm -rf /var/www/html && sudo mv wordpress /var/www/html
# Generate WordPress secrets (Copy them somewhere)
$ curl -s https://api.wordpress.org/secret-key/1.1/salt/
# Edit WordPress configuration
$ sudo mv /var/www/html/wp-config-sample.php /var/www/html/wp-config.php && sudo nano /var/www/html/wp-config.php
These are WordPress configuration parameters that you need to adjust in order to establish a connection between your application and the database.
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'wordpressuser');
/** MySQL database password */
define('DB_PASSWORD', 'password');
/** MySQL hostname */
define('DB_HOST', 'localhost');
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8mb4');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');
/**
* Authentication Unique Keys and Salts.
*
* Change these to different unique phrases!
* You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
* You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
*
* @since 2.6.0
*/
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
define('LOGGED_IN_KEY', 'put your unique phrase here');
define('NONCE_KEY', 'put your unique phrase here');
define('AUTH_SALT', 'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT', 'put your unique phrase here');
define('NONCE_SALT', 'put your unique phrase here');
Finally, set the permissions right. These values are a baseline; production deployments may use stricter ownership depending on how code is deployed and how updates are handled.
$ sudo chown -R www-data:www-data /var/www/html/ && sudo chmod -R 755 /var/www/html/
Step 8: Obtain and configure the SSL Certificate with Let's Encrypt
# First, add the Certbot repository
$ sudo add-apt-repository ppa:certbot/certbot
# Install Certbot and Certbot Nginx plugin
$ sudo apt-get update && sudo apt-get install python-certbot-nginx
# Obtain your free Let’s Encrypt SSL/TLS certificate
$ sudo certbot --nginx -m your@email.com -d your_domain.com
Follow the configuration steps followed by the command above and
when it comes to Choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
choose 2
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
After that, Let’s Encrypt Client has installed your certificate and configured your website to redirect all traffic to HTTPS.
If you check your site’s Nginx configuration file, /etc/nginx/sites-available/your_domain.com, you’ll notice
that some modifications have been made by the Let’s Encrypt client.
. . .
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = your_domain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name your_domain.com;
return 404; # managed by Certbot
}
Your WordPress site is now ready to be used over HTTPS.
Step 9: Finalize the WordPress Installation
The final step is to complete the WordPress installation through the web interface.
To do this, just navigate in your browser to your domain (https://your_domain.com),
and you’ll be guided through the easy process of installing WordPress.
Post-install verification
Before calling the server production-ready, verify:
sudo ufw statusshows only SSH, HTTP, and HTTPS exposed.sudo nginx -tpasses and Nginx reloads cleanly.sudo systemctl status nginxandsudo systemctl status php*-fpmare healthy.sudo certbot renew --dry-runsucceeds.- WordPress admin login works over HTTPS.
- Media uploads work and files are owned by the expected web server user.
- The site URL and home URL use
https://. - Backups cover both the database and
/var/www/html/wp-content/.
Next hardening steps
This guide gets the site online. For a production WordPress instance, continue with:
- SSH key-only login and no root login
- unattended security updates or a patching schedule
- fail2ban or equivalent login-rate protection
- regular database and file backups with restore tests
DISALLOW_FILE_EDITinwp-config.php- PHP execution blocked in uploads
- monitoring for certificate expiry, disk space, and PHP-FPM failures
For Apache-based deployments, see Hardening WordPress on LAMP (Apache) for a broader hardening checklist. Several controls there also apply to Nginx.
Related work
This secure LEMP baseline supports editorial and CMS platform delivery in Multilingual news platform and Large crowdlearning platform.