Skip to content

Instantly share code, notes, and snippets.

@moh8med
Created January 4, 2023 05:55
Show Gist options
  • Save moh8med/9dbeea1c05a37ae9f32a245bec7da0d5 to your computer and use it in GitHub Desktop.
Save moh8med/9dbeea1c05a37ae9f32a245bec7da0d5 to your computer and use it in GitHub Desktop.
Install the LEMP Stack on Ubuntu 22.04 LTS.

Initial Server Setup with Ubuntu 22.04

Introduction

When you first create a new Ubuntu 22.04 server, you should perform some important configuration steps as part of the basic setup. These steps will increase the security and usability of your server, and will give you a solid foundation for subsequent actions.

See: https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-22-04

Step 1 — Logging in as root

If you are not already connected to your server, log in now as the root user using the following command (substitute the highlighted portion of the command with your server’s public IP address):

ssh root@your_server_ip

Step 2 — Creating a new user

This example creates a new user called acme:

adduser acme

Step 3 — Granting Administrative Privileges

As root, run these commands to add your new user to the sudo and www-data groups:

usermod -aG sudo acme

# Also add the newly created user to www-data group
usermod -aG www-data acme

Step 4 — Generating a New SSH Key for the User acme

First, change the current user to acme:

su - acme

As acme, run this command to generate a new SSH key on the server:

ssh-keygen -t rsa -b 4096

Add the content from the public key file i.e. .pub to your github repository, settings > deploy keys > Add deploy keys:

cat ~/.ssh/id_rsa.pub

Switch back to the root user to complete the installation:

exit

Step 5 — Installing NGINX Web Server

# Add PPA for NGINX Stable with HTTP/2
sudo add-apt-repository -y ppa:ondrej/nginx
sudo apt update

sudo apt -y install nginx
sudo systemctl enable nginx.service

Step 6 — Setting Up a Basic Firewall

As root, run these commands to enable the firewall and allow NGINX and OpenSSH ports only:

# List available apps
sudo ufw app list

# Allow 22, 80 and 443 ports only
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'

# Start the firewall
sudo ufw --force enable

# Check the status
sudo ufw status

Step 7 — Installing Fail2Ban for SSH Brute-force Protection

sudo apt -y install fail2ban

Overriding Fail2Ban Settings

Open this file with your preferred text editor:

sudo nano /etc/fail2ban/jail.local

Paste in the following configuration:

[DEFAULT]

ignoreip = 127.0.0.1/8 ::1

# "bantime" is the number of seconds that a host is banned.
bantime  = 6h

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 30m

# "maxretry" is the number of failures before a host get banned.
maxretry = 3

[sshd]
enabled = true

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = %(nginx_access_log)s
maxretry = 2

[nginx-limit-req]
enabled = true
port    = http,https
logpath = %(nginx_error_log)s
maxretry = 2
bantime  = 12h

You can test your configuration for syntax errors by typing:

sudo fail2ban-client -t

If any errors are reported, go back to your configuration file to review its contents before continuing.

When you are ready, restart Fail2ban service to enable the changes:

sudo systemctl restart fail2ban.service

Step 8 — Tuning The Linux Kernel

If you plan on handling a large number of connections in a high performance environment, we recommend tuning the following kernel parameters:

sudo sysctl net.ipv4.tcp_syncookies=1
echo 'net.ipv4.tcp_syncookies=1' | sudo tee -a /etc/sysctl.conf

sudo sysctl vm.swappiness=1
echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf

sudo sysctl vm.vfs_cache_pressure=50
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf

sudo sysctl vm.overcommit_memory=1
echo 'vm.overcommit_memory=1' | sudo tee -a /etc/sysctl.conf

sudo sysctl net.ipv4.tcp_max_syn_backlog=65535
echo 'net.ipv4.tcp_max_syn_backlog=65535' | sudo tee -a /etc/sysctl.conf

sudo sysctl net.core.somaxconn=65535
echo 'net.core.somaxconn=65535' | sudo tee -a /etc/sysctl.conf

Install The LEMP Stack on Ubuntu 22.04

Introduction

The LEMP software stack is a group of software that can be used to serve dynamic web pages and web applications written in PHP. This is an acronym that describes a Linux operating system, with an Nginx (pronounced like “Engine-X”) web server. The backend data is stored in the MySQL database and the dynamic processing is handled by PHP.

This guide demonstrates how to install a LEMP stack on an Ubuntu 22.04 server. The Ubuntu operating system takes care of the Linux portion of the stack. We will describe how to get the rest of the components up and running.

See: https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-on-ubuntu-22-04

Step 1 — Installing PHP 8.1 Extensions

You have Nginx installed to serve your content. Now you can install PHP to process code and generate dynamic content for the web server.

# Add PPA for PHP
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update

sudo apt -y install php8.1-bcmath php8.1-bz2 php8.1-cli php8.1-curl php8.1-fpm php8.1-gd php8.1-gmp php8.1-intl php8.1-json php8.1-mbstring php8.1-mysql php8.1-odbc php8.1-opcache php8.1-xml php8.1-zip php8.1-xsl

Installing Composer

To install Composer pacakge manager, run the following commands:

sudo curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chown acme:acme /usr/local/bin/composer

Installing ImageMagick extension

sudo apt -y install php8.1-imagick

Image Optimization Tools

The laravel-medialibrary package will use these tools to optimize converted images if they are present on our system.

Here's how to install all the optimizers on Ubuntu:

sudo apt -y install jpegoptim optipng pngquant gifsicle webp

Modifying the PHP Configuration for better performance and security

Open this file with your preferred text editor

sudo nano /etc/php/8.1/fpm/pool.d/www.conf

Paste in the following configuration:

[www]

user = acme
group = acme

listen = /run/php/php8.1-fpm.sock
; listen = 127.0.0.1:9000

listen.owner = www-data
listen.group = www-data
;listen.mode = 0660
; listen.allowed_clients = 127.0.0.1

;listen.backlog = 511
listen.backlog = 4096
rlimit_files = 4096

pm = static
pm.max_children = 16
pm.max_requests = 256
pm.status_path = /status

;security.limit_extensions = .php .php3 .php4 .php5 .php7
security.limit_extensions = .php

php_admin_flag[expose_php] = off
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
; php_admin_value[error_log] = /var/log/fpm-php.www.log

php_admin_value[log_level] = warning

php_admin_flag[session.cookie_secure] = on
php_admin_flag[session.cookie_httponly] = on

php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 25M
php_admin_value[post_max_size] = 250M
php_admin_value[max_input_vars] = 1500

request_terminate_timeout = 60
php_admin_value[max_execution_time] = 60

php_admin_value[user_ini.filename] = 

php_admin_value[opcache.memory_consumption] = 192
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 30000
php_admin_flag[opcache.validate_timestamps] = off
php_admin_value[opcache.revalidate_freq] = 3
php_admin_flag[opcache.save_comments] = off

php_admin_value[realpath_cache_size] = 8192k

php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare

You can test your configuration for syntax errors by typing:

sudo php-fpm8.1 -t

If any errors are reported, go back to your configuration file to review its contents before continuing.

When you are ready, restart PHP-FPM service to enable the changes:

sudo systemctl restart php8.1-fpm.service

Step 2 — Configuring NGINX to Use the PHP Processor

On Ubuntu 22.04, Nginx has one server block enabled by default and is configured to serve documents out of a directory at /var/www/html.

Open the default file in Nginx’s sites-available directory using your preferred command-line editor. Here, we’ll use nano:

sudo nano /etc/nginx/sites-available/default

Paste in the following bare-bones configuration:

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

    root /var/www/html;

    server_name _;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    # Add index.php to the list if you are using PHP
    index index.php index.html index.htm;

    charset utf-8;

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        # try_files $uri $uri/ =404;
        try_files $uri $uri/ /index.php;
    }

    # pass PHP scripts to FastCGI server
    #
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;

        # With php-fpm (or other unix sockets):
        fastcgi_pass unix:/run/php/php8.1-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;
    }
}

You can test your configuration for syntax errors by typing:

sudo nginx -t

If any errors are reported, go back to your configuration file to review its contents before continuing.

When you are ready, reload Nginx to apply the changes:

sudo nginx -s reload

Step 3 — Installing MySQL Server

sudo apt -y install mysql-server-8.0

When the installation is finished, it’s recommended that you run a security script that comes pre-installed with MySQL. This script will remove some insecure default settings and lock down access to your database system. Start the interactive script by running:

# Improve the security of MySQL installation
sudo mysql_secure_installation

Configuring MySQL

Open this file with your preferred text editor:

sudo nano /etc/mysql/conf.d/custom.cnf

Paste in the following configuration:

[mysqld]

# GENERAL #
user                           = mysql
default-storage-engine         = InnoDB
socket                         = /var/run/mysqld/mysqld.sock
pid-file                       = /var/run/mysqld/mysqld.pid
performance_schema             = off

sql-mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

# MyISAM #
key-buffer-size                = 32M

# SAFETY #
max-allowed-packet             = 16M
max-connect-errors             = 1000000

# DATA STORAGE #
datadir                        = /var/lib/mysql/

# BINARY LOGGING #
skip-log-bin

# CACHES AND LIMITS #
tmp-table-size                 = 32M
max-heap-table-size            = 32M
max-connections                = 150
thread-cache-size              = 50
open-files-limit               = 65535
table-definition-cache         = 1024
table-open-cache               = 2048

skip-name-resolve = 1

# INNODB #
innodb-flush-method            = O_DIRECT
innodb-log-files-in-group      = 2
innodb-log-file-size           = 16M
innodb-flush-log-at-trx-commit = 1
innodb-file-per-table          = 1
innodb-buffer-pool-size        = 128M

# Metadata statistic updates can impact strongly performance
innodb_stats_on_metadata = 0;

# LOGGING #
log-error                      = /var/log/mysql/error.log
log-queries-not-using-indexes  = 0
slow-query-log                 = 0
slow-query-log-file            = /var/log/mysql/slow.log

[mysqldump]
# Variable reference
# For MySQL 8.0: https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html
# For MariaDB:   https://mariadb.com/kb/en/library/mysqldump/
quick
quote_names
max_allowed_packet              = 64M

Restart MySQL service to enable the changes:

sudo systemctl restart mysql.service

(Important) Adjusting User Authentication and Privileges

In Ubuntu systems running MySQL 5.7 (and later versions), the root MySQL user is set to authenticate using the auth_socket plugin by default rather than with a password. This allows for some greater security and usability in many cases, but it can also complicate things when you need to allow an external program (e.g., phpMyAdmin) to access the user.

In order to use a password to connect to MySQL as root, you will need to switch its authentication method from auth_socket to mysql_native_password. To do this, open up the MySQL prompt from your terminal:

sudo mysql

To configure the root account to authenticate with a password, run the following ALTER USER command. Be sure to change password to a strong password of your choosing:

mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

Then, run FLUSH PRIVILEGES which tells the server to reload the grant tables and put your new changes into effect:

mysql> FLUSH PRIVILEGES;

Step 4 — Installing phpMyAdmin

We need phpMyAdmin to manage our MySQL database. Run the following commands to install phpMyAdmin to easily manage MySQL databases:

# Install required dependencies
sudo apt -y install wget zip unzip

# Get the latest version URL from here:
# https://www.phpmyadmin.net/downloads/

# Download phpMyAdmin in `/var/www`
sudo wget https://files.phpmyadmin.net/phpMyAdmin/5.2.0/phpMyAdmin-5.2.0-english.zip -O /var/www/pma.zip

cd /var/www

# Extract the downloaded file to a new directory (/var/www/pma)
sudo unzip pma.zip && sudo rm pma.zip && sudo mv phpMyAdmin-5.2.0-english pma

# Create ./tmp directoy
sudo mkdir /var/www/pma/tmp && sudo chown acme:acme /var/www/pma/tmp

# Modify default configuration
sudo cp /var/www/pma/config.sample.inc.php /var/www/pma/config.inc.php

sudo nano /var/www/pma/config.inc.php
#
# Make sure to add a 32 chars long key here...
#
# /**
#  * This is needed for cookie based authentication to encrypt password in
#  * cookie. Needs to be 32 chars long.
#  */
# $cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */

# Change files owner and permissions
sudo find /var/www/pma -type d -exec chmod 755 {} \;
sudo find /var/www/pma -type f -exec chmod 644 {} \;

# When you're all done, link phpMyAdmin to the root directoy of your PHP project,
# example: sudo ln -s /var/www/pma /var/www/laravel/public/

Step 5 — Installing Redis (Optional)

Redis is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, and sorted sets.

# Add PPA for Redis
sudo add-apt-repository -y ppa:redislabs/redis
sudo apt update

sudo apt -y install redis
sudo systemctl enable redis-server.service

Installing the Redis PHP extension

Before using Redis with Laravel, we need to install and use the PhpRedis PHP extension.

sudo apt -y install php8.1-redis

Follow the steps on the Laravel documentation pages https://laravel.com/docs/9.x/redis#introduction https://laravel.com/docs/9.x/queues#driver-prerequisites

Configuring Redis

Open this file with your preferred text editor:

sudo nano /etc/redis/redis.conf

Inside the file, find these directives and replace them with the following:

# Set the max number of connected clients at the same time. By default
maxclients 20000

# TCP listen() backlog.
# As the comment in redis.conf indicates, the value of `somaxconn` and `tcp_max_syn_backlog` may need to be increased at the OS level as well.
tcp-backlog 65535

# TCP keepalive.
tcp-keepalive 300

# Set a memory usage limit to the specified amount of bytes.
maxmemory 1gb

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory.
maxmemory-policy volatile-lru

# Save the DB to disk every 1 hour.
save 3600 1

Kernel Memory

Under high load, occasional performance dips can occur due to memory allocation. This is something Salvatore, the creator of Redis, blogged about in the past. The performance issue is related to transparent hugepages, which you can disable at the OS level if needed.

echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled

Step 6 — Installing Supervisor (Optional)

Supervisor is a process monitor for the Linux operating system, and will automatically restart your queue:work process if it fails. More information here.

Installing Supervisor

sudo apt -y install supervisor

Configuration file example:

Create a new worker configuration file:

sudo nano /etc/supervisor/conf.d/laravel-worker.conf

Use the following example code:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/staging/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=acme
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/staging/storage/logs/worker.log
stopwaitsecs=3600

Starting Supervisor

Once the configuration file has been created, you may update the Supervisor configuration and start the processes using the following commands:

sudo supervisorctl reread
 
sudo supervisorctl update
 
sudo supervisorctl start laravel-worker:*

Where To Go From Here?

At this point, you have a solid foundation for your server. You can install any of the software you need on your server now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment