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
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
This example creates a new user called acme
:
adduser acme
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
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
# 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
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
sudo apt -y install fail2ban
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
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
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.
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
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
sudo apt -y install php8.1-imagick
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
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
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
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
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
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;
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/
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
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
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
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
Supervisor is a process monitor for the Linux operating system, and will automatically restart your queue:work
process if it fails.
More information here.
sudo apt -y install supervisor
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
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:*
At this point, you have a solid foundation for your server. You can install any of the software you need on your server now.