Skip to content

Instantly share code, notes, and snippets.

@justinhartman
Last active January 17, 2024 00:46
Show Gist options
  • Save justinhartman/73c9266fbb5b01d1d9031263c695be66 to your computer and use it in GitHub Desktop.
Save justinhartman/73c9266fbb5b01d1d9031263c695be66 to your computer and use it in GitHub Desktop.
Setup Debian 10 LEMP Virtual Machine or Server

How To Install Nginx, MySQL, PHP, Redis and more on a Debian 10 Virtual Machine

This series of documents will configure and setup a Nginx, MySQL, and PHP (LEMP) server on a pretty feature-rich VM running Debian. I used a VM with 8GB RAM and 4 CPU Cores so YMML with some of the settings below.

This will also install other useful packages and configurations like, Redis, Memcached, Node, NPM, Composer, and a fully automated SSL service using certbot for Let's Encrypt.

These documents take you through each step of the configuration from:

  1. General Setup
  2. Install Nginx, MySQL, and PHP (LEMP)
  3. Confiure PHP and Nginx
  4. How to fine-tune PHP-FPM
  5. Fine-tune MySQL/MariaDB for Performance
  6. Remove systemd process limits
  7. Create a New Nginx Host
  8. Setup Let's Encrypt SSL certificates
  9. Setup Remote SSH Login with sudo
  10. Install Redis & Memcached Servers
  11. Install Composer & Node/NPM

General Setup

These are a series of specific commands to configure the Debian server for our environment. You may not need to do these.

$ sudo apt-get install locales-all
$ sudo dpkg-reconfigure locales
$ nano ~/.bash_profile

Add these:

# Fixes locale issues.
export LANGUAGE=en_GB.UTF-8
export LANG=en_GB.UTF-8
export LC_ALL=en_GB.UTF-8
export LC_TERMINAL=en_GB.UTF-8

Change Timezone

$ sudo cp /usr/share/zoneinfo/Europe/London /etc/localtime # set time

Change Hostname

$ sudo hostnamectl set-hostname example.com
$ sudo nano /etc/hosts # add hostname with IP address (e.g. 192.168.1.1 example.com)
$ hostnamectl
   Static hostname: example.com
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 62bea8bdf6794fb691851667ac2f9818
           Boot ID: dae700b6e5bb44fcb5d4683f05f30919
    Virtualization: vmware
  Operating System: Debian GNU/Linux 10 (buster)
            Kernel: Linux 4.19.0-17-amd64
      Architecture: x86-64

Install Nginx, MySQL, and PHP (LEMP)

There are three steps to install the LEMP server and then there are some configuration changes which you need to do.

Step 1 - Install Nginx

$ sudo apt update
$ sudo apt install nginx
$ sudo systemctl start nginx
$ sudo systemctl enable nginx
$ sudo chown -R www-data:www-data /usr/share/nginx/html /var/www

Step 2 - Install MariaDB

$ sudo apt install mariadb-server mariadb-client
$ sudo systemctl start mariadb
$ sudo systemctl enable mariadb
$ sudo mysql_secure_installation # answer N to validate password plugin
$ sudo mysql

Configure MySQL users

mysql> SELECT user,authentication_string,plugin,host FROM mysql.user;
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
mysql> FLUSH PRIVILEGES;
mysql> SELECT user,authentication_string,plugin,host FROM mysql.user;
mysql> exit

Step 3 - Install PHP-FPM

I've chosen to install PHP 8.0 but you can change any of the commands from php8.0 to php7.4 as needed. For example any reference to php8.0-fpm can be adjusted simply to php7.4-fpm.

$ sudo apt-get update
$ sudo apt -y install lsb-release apt-transport-https ca-certificates wget
$ wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
$ echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list
$ sudo apt update
$ sudo apt install curl unzip zip
$ sudo apt -y install php8.0-{fpm,cli,apc,bcmath,bz2,curl,gd,imagick,intl,memcached,mbstring,mysql,redis,xml,zip}
$ sudo systemctl start php8.0-fpm
$ sudo systemctl enable php8.0-fpm

Configure PHP-FPM and Nginx

These are the different configuration files for setting up the servers.

Configure php.ini

$ sudo nano /etc/php/8.0/fpm/php.ini

OR for PHP 7.4:

$ sudo nano /etc/php/7.4/fpm/php.ini

Change these values:

cgi.fix_pathinfo=0
post_max_size=256M
upload_max_filesize=256M
date.timezone=Europe/London
[opcache]
opcache.memory_consumption=256
opcache.max_accelerated_files=50000
opcache.validate_timestamps=0
opcache.revalidate_freq=120
opcache.error_log=/var/log/opcache-errors.log
[apc]
apc.shm_size=128M
apc.stat=1
; Relative to the number of cached files (you may need to watch your stats for
; a day or two to find out a good number).
apc.num_files_hint=7000
; The number of seconds a cache entry is allowed to idle in a slot before APC
; dumps the cache.
apc.ttl=7200
apc.user_ttl=7200
apc.include_once_override=0
; Allow 2 seconds after a file is created before it is cached to prevent users
; from seeing half-written/weird pages.
apc.file_update_protection=2
; Ignore files
apc.filters = "/var/www/html/apc.php"
apc.cache_by_default=1
apc.use_request_time=1
apc.slam_defense=0
apc.stat_ctime=0
apc.canonicalize=1
apc.write_lock=1
apc.report_autofilter=0
apc.rfc1867=0
apc.rfc1867_prefix=upload_
apc.rfc1867_name=APC_UPLOAD_PROGRESS
apc.rfc1867_freq=0
apc.rfc1867_ttl=3600
apc.lazy_classes=0
apc.lazy_functions=0
$ sudo systemctl reload php8.0-fpm

Configure php-fpm.conf

$ sudo nano /etc/php/8.0/fpm/php-fpm.conf

OR for PHP 7.4:

$ sudo nano /etc/php/7.4/fpm/php-fpm.conf
emergency_restart_threshold=10
emergency_restart_interval=1m
process_control_timeout=10s
$ sudo systemctl reload php8.0-fpm

Configure nginx

$ sudo nano /etc/nginx/nginx.conf

Change these values:

client_max_body_size 256M;
keepalive_timeout 2;
server_tokens off;
$ sudo systemctl reload nginx

Edit default host to enable PHP-FPM

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

Change only these values in the host:

server {
    index index.php index.html index.htm index.nginx-debian.html;
    location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php8.0-fpm.sock;
    }
}

Test the output and reload.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx

How to fine-tune PHP-FPM

This section outlines how to go about configuring your php-fpm www.conf pool file. It may seem complicated and overwhelming and you can totally skip this if you want to. That said, overlooking this step is like buying a sports car and never going above 20 mph; you're going to kill the engine by not using the car correctly. You really do want to maximise your server as much as possible to avoid bottlenecks and crashes later on.

An Overview

You are probably going to want to read this article on FPM tuning which I used to determine the settings below. The article will help you scientifically calculate the exact settings you should use for max_children, start_servers, min_spare_servers, and max_spare_servers. The most complicated one to calculate is max_children because there is a formula he uses to do this which is (Total RAM - Memory used on server) / Process Size = max_children. I've tried my best to give you the succinct version of what to calculate, as well as how, but depending on your level of skill, you may need to read the article fully.

There is also reference to a script called ps_mem.py which, when run on your server, will give you the total Process Size needed in the max_children formula above.

Calculations

Here are the settings and calculations. The Value column has details on how to calculate the Setting value.

Setting Value
max_children (Total RAM - Memory used on server) / Process Size
start_servers Number of CPU cores x 4
min_spare_servers Number of CPU cores x 2
max_spare_servers Equal to same value as start_servers

How to calculate max_children

Let's assume, for this example, you have a server with 8GB RAM and 4 CPU Cores. With this in mind we can continue to work this formula out:

(Total RAM - Memory used on server) / Process Size = max_children

First let's run some commands on the server to get the data we need for the formula.

Memory Usage

We need to know how much total memory we have as well as how much of the memory is used. The rest is irrelevant.

$ sudo free -hl
              total        used        free      shared  buff/cache   available
Mem:          7.8Gi       331Mi       6.0Gi        27Mi       1.5Gi       7.1Gi

Process Memory

You're now going to want to download the ps_mem.py file and run the command. When done we are only interested in the total amount the sum calculates; i.e. the total amount in MiB after the = sign.

$ cd ~/
$ wget https://raw.githubusercontent.com/pixelb/ps_mem/master/ps_mem.py
$ sudo python ps_mem.py | grep php-fpm
20: 24.8 MiB +   9.0 MiB =  33.7 MiB	php-fpm8.0 (3)

Work It Out

We've got everything we need to apply these values to the formula as follows:

  • Total RAM = 7800 is the total column value (7.8Gi) when running sudo free -hl
  • Memory Used = 331 is the used column value (331Mi) when running sudo free -hl
  • Process Size = 33.7 is the total value (33.7 MiB) after executing sudo python ps_mem.py | grep php-fpm

Pull this all together and run the formula of (Total RAM - Memory used on server) / Process Size which will give you something similar to this:

(7800 - 331) / 33.7 = 221.63

Therefore, we now know that max_children = 222 👍🏿

The other calculations are now easy because they are all based on how many CPU Cores your have. Open up the www.conf file and begin editing with your newly calculated values.

Edit the Settings

Change the www pool so it now has the following settings.

$ sudo nano /etc/php/8.0/fpm/pool.d/www.conf

OR for PHP 7.4:

$ sudo nano /etc/php/7.4/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
; listen = /run/php/php7.4-fpm.sock
listen = /run/php/php8.0-fpm.sock
listen.owner = www-data
listen.group = www-data

; Server Resources: 8G RAM | 4x CPU Cores
; `free -hl` output: Total 7.8Gi   Used 331Mi    Free 5.8Gi
; `ps_mem.py` output: 24.8 MiB + 9.0 MiB = 33.7 MiB
pm = dynamic
; (Mem - Used) / ps_mem.py = max_children
; (7800 - 331) / 33.7      = 221.63
pm.max_children = 222
; CPU * 4 = 16
pm.start_servers = 16
; CPU * 2 = 8
pm.min_spare_servers = 8
; Same as start_servers
pm.max_spare_servers = 16
pm.max_requests = 500

Reload PHP:

$ sudo systemctl reload php8.0-fpm # or replace with php7.4-fpm

Fine-tune MySQL/MariaDB for Performance

Increase Open Files Limit

To ensure good server performance, the total number of client connections, database files, and log files must not exceed the maximum file descriptor limit on the operating system ulimit -n. Linux systems limit the number of file descriptors that any one process may open to 1,024 per process. On active database servers (especially production ones) it can easily reach the default system limit.

To increase this, edit /etc/security/limits.conf and specify or add the following:

# Added to increase server limit of 1024 per process.
mysql            soft    nofile          65535
mysql            hard    nofile          65535
# Uncomment this if ulimit doesn't change from 1024
#*                soft    nofile          65535

This requires a system restart. Once rebooted, check the limit with:

$ ulimit -Sn
65535
$ ulimit -Hn
65535

Alternatively, you could set system-wide file and process limits with the following configuration:

# /etc/security/limits.conf
# Added to increase server limit of 1024 per process.
*               soft    nofile          102400
*               hard    nofile          102400
*               soft    nproc           10240
*               hard    nproc           10240

NB: Your milage may vary so please only use the above with caution!

Setting Swappiness on Linux for MariaDB

$ sudo sysctl -w vm.swappiness=1
$ sudo nano /etc/sysctl.conf

Add this line to the file: vm.swappiness=1

Filesystem Optimisations for MariaDB

Most database setups do not need to record file access time. You might want to disable this when mounting the volume into the system. To do this, edit your file /etc/fstab. For example, on a volume named /dev/sda1, this how it looks like

/dev/sda1    /    ext4    noatime,errors=remount-ro    0    1

Tuneup MariaDB To Utilise Memory Efficiently

You have these settings to consideration when fine-tuning your server:

  • innodb_buffer_pool_size: You can set this from 70-80% of the total available memory on a dedicated database server with only or primarily InnoDB tables. If set to 2 GB or more, you will probably want to adjust innodb_buffer_pool_instances as well. You can set this dynamically if you are using MariaDB >= 10.2.2 version.
  • innodb_flush_log_at_trx_commit: This setting offers a significant trade-off between performance and reliability. When set to 0, database performance is greatly increased. However, up to 1 second of transactions can be lost in a crash. The default value for this setting is 1.
  • innodb_flush_method: Set this to O_DIRECT in order to avoid double buffering data.
  • innodb_log_file_size: If you are preferring more performance gains especially during and handling your InnoDB transactions, setting the variable innodb_log_file_size to a larger value such as 5Gib or even 10GiB is reasonable. Increasing means that the larger transactions can run without needing to perform disk I/O before committing.
  • max_connections: If you are application requires a lot of concurrent connections, you can start setting this to 500.
  • max_allowed_packet: The max_allowed_packet directive defines the maximum size of packet that can be sent. If the client sends more data than max_allowed_packet size, the socket will be closed. Ideally, especially on most application demands today, you can start setting this to 512M.
  • back_log: Increase if you expect short bursts of connections. Cannot be set higher than the operating system limit. To calculate use (50 + max_connections/5) to get total value.
  • Query Cache: Preferably you should disable query cache in all of your MariaDB setup. You need to ensure that query_cache_type=OFF and query_cache_size=0 to complete disable query cache. Unlike MySQL, MariaDB is still completely supporting query cache and do not have any plans on withdrawing its support to use query cache.
  • Turn on slow_query_log and se the long_query_time to 1 second to log queries to /var/lib/mysql/hostname-slow.log.
$ sudo nano /etc/mysql/my.cnf
# /etc/mysql/my.cnf
[mysqld]
open_files_limit               = 102400
innodb_buffer_pool_size        = 3G
# innodb_buffer_pool_instances   = 4 (default is 1)
innodb_flush_log_at_trx_commit = 0
innodb_flush_method            = O_DIRECT
innodb_log_file_size           = 1G
max_connections                = 5000
# back_log = (max_connections/5) + 50
back_log                       = 1050
max_allowed_packet             = 256M
# Disable query cache
query_cache_type               = OFF
query_cache_size               = 0
slow_query_log                 = ON
long_query_time                = 1
slow_query_log_file            = /var/lib/mysql/slow.log

Remove systemd process limits

There are now also some issues with systemd limiting file processes. Have a look at the following:

$ sudo systemctl status redis-server
● redis-server.service - Advanced key-value store
   Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2021-08-01 23:24:48 BST; 13min ago
     Docs: http://redis.io/documentation,
           man:redis-server(1)
 Main PID: 571 (redis-server)
    Tasks: 4 (limit: 4915)
   Memory: 9.7M
   CGroup: /system.slice/redis-server.service
           └─571 /usr/bin/redis-server 127.0.0.1:6379

If you see the Tasks: 4 (limit: 4915) has a limit set of 4915. This is how the Linux Kernel works and it defaults to 15%, which equals 4915 with the kernel's defaults on the host.

In order to check what your system is capable of it's a rather easy calculation.

$ more /proc/sys/kernel/pid_max
32768

You now take this number and work out 15% of the value, i.e. 32768*15/100 = 4915.2 and you can see how this is calculated.

That said, you can override this for certain Debian-based services and here's how we change them for those services.

MariaDB

$ sudo nano /etc/systemd/system/mysqld.service
[Service]
TasksMax=infinity
LimitNOFILE=65535

PHP

$ sudo nano /lib/systemd/system/php8.0-fpm.service
[Service]
TasksMax=infinity

Nginx

$ sudo nano /lib/systemd/system/nginx.service
[Service]
TasksMax=infinity

Redis

$ sudo nano /lib/systemd/system/redis-server.service
[Service]
TasksMax=infinity

Memcached

$ sudo nano /lib/systemd/system/memcached.service
[Service]
TasksMax=infinity

Restarting systemctl

When you're done making these changes you have to restart systemctl with sudo systemctl daemon-reload in order for the file limits to be removed.

Create a New Nginx Host

It's time to setup your domain on nginx and this is how you go about enabling it and ensuring PHP-FPM is working for the host.

$ sudo nano /etc/nginx/sites-available/example.com
server {
        listen 80;
        root /var/www/example.com;
        index index.php index.html index.htm index.nginx-debian.html;
        server_name example.com www.example.com;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                # fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
                fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
        }

        location ~ /\.ht {
                deny all;
        }
}
$ sudo mkdir -p /var/www/example.com
$ sudo chown -R www-data:www-data /var/www/example.com
$ sudo chmod -R g+w /var/www/example.com
$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx

Setup Let's Encrypt SSL certificates

$ sudo apt-get install python-certbot-nginx
$ sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
$ sudo mkdir -p /var/www/_letsencrypt
$ sudo chown www-data /var/www/_letsencrypt
$ sudo certbot certonly --webroot -d example.com -d www.example.com --email info@example.com -w /var/www/_letsencrypt -n --agree-tos --force-renewal
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
Using the webroot path /var/www/_letsencrypt for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/example.com/privkey.pem
   Your cert will expire on 2022-02-22. 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"

Configure Host

$ sudo nano /etc/nginx/sites-available/example.com

Add (or change) the following SSL directives in the Nginx host:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # ACME-challenge
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/_letsencrypt;
    }
    
    # Redirect to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

Reload Nginx

$ sudo nginx -t && sudo systemctl reload nginx

Renew Certificates

You will now need to create a post hook script for certbot so that it can reload Nginx once a domain has been renewed. Luckily I have created a script for you to do this and you can access the script over here. Setting up this script as is a simple 3 step process.

Step 1

First create the nginx-reload.sh post hook file:

$ sudo nano /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh

Step 2

You are going to want to copy the code of the script by clicking here and head on over and paste it in your /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh file.

Step 3

Once you have the file saved, make it executable and reload nginx.

$ sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
$ sudo nginx -t && sudo systemctl reload nginx

Done. The script is now setup to begin working automatically in the background. Certbot runs a cronjob twice daily and if a certificate is renewed your newly created post hook script will be executed and run.

Success Output

Executing the nginx-reload.sh file without any errors in any of your nginx config or virtual host files will output the following success messages to your console:

$ sudo /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
Removing old log file.
Success: Reloading nginx server.

Similarly, when the certbot cronjob executes and runs this post hook script the following successful output will be seen.

$ sudo certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
Output from nginx-reload.sh:
Removing old log file.
Success: Reloading nginx server.

Error Output

Executing the nginx-reload.sh file with an error in either your nginx config or virtual hosts files will output the following error and log file:

$ sudo /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
Removing old log file.
Fail: Error with config file.
Aborting nginx restart.
Log file output:
nginx: [emerg] "worker_connections" directive is not allowed here in /etc/nginx/nginx.conf:7
nginx: configuration file /etc/nginx/nginx.conf test failed

Similarly, when the certbot cronjob executes and runs this post hook script the following output will be seen.

$ sudo certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/example.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Running post-hook command: /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
Output from nginx-reload.sh:
Removing old log file.
Fail: Error with config file.
Aborting nginx restart.
Log file output:
nginx: [emerg] "worker_connections" directive is not allowed here in /etc/nginx/nginx.conf:7
nginx: configuration file /etc/nginx/nginx.conf test failed

Hook command "/etc/letsencrypt/renewal-hooks/post/nginx-reload.sh" returned error code 1

Setup Remote SSH Login with sudo

This uses public/private SSH key for authentication.

$ sudo apt-get install openssh-server
$ sudo useradd -m USERNAME -g www-data # add user to group they can write to www folder
$ sudo passwd USERNAME # set password for login
$ sudo chmod -R g+w /var/www # set group write permissions
$ sudo mkdir -p /home/USERNAME/.ssh
$ sudo nano /home/USERNAME/.ssh/authorized_keys # copy your `~/.ssh/id_rsa.pub` key from computer and paste here
$ sudo chmod -R go= /home/USERNAME/.ssh
$ sudo chown -R USERNAME:www-data /home/USERNAME
$ sudo usermod -a -G sudo USERNAME # optionally, add user to sudoers file
$ sudo service ssh restart

Now login to the SSH server with your private key and test:

$ ssh -i ~/.ssh/id_rsa USERNAME@server.com
Linux server.com 4.19.0-13-amd64 #1 SMP Debian 4.19.160-2 (2020-11-28) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Dec  8 23:12:02 2020 from 104.245.101.122
USERNAME@server:~#

If all goes well you won't be prompted for a password and you will just login straight away. If you are asked for a password you have done something wrong. Try again.

Turn off sudo password authentication

With the above configured you will still be prompted for your password when running a sudo command on the server. To disable the prompting of passwords, do the following.

$ sudo visudo

Add your username to the end of the file so it looks similar to:

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL
#includedir /etc/sudoers.d
USERNAME  ALL=(ALL) NOPASSWD: ALL

NB: replace USERNAME with the username you created, don't leave it as is. Then save the file and restart the ssh server.

$ sudo service ssh restart

Install and Setup Redis & Memcached Servers

Install Redis

$ sudo apt install redis-server
$ sudo systemctl start redis-server
$ sudo systemctl enable redis-server

Edit Configuration

$ sudo nano /etc/redis/redis.conf

Change these settings:

supervised systemd # use Debian's systemd
bind 127.0.0.1 ::1
# If you want to secure your Redis server set a password below.
# requirepass change-this-password

Restart Redis

$ sudo systemctl restart redis-server
$ sudo systemctl status redis-server

Install Memcached

$ sudo apt install memcached libmemcached-tools
$ sudo systemctl start memcached
$ sudo systemctl enable memcached

Edit Configuration

$ sudo nano /etc/memcached.conf

Change at least this setting:

# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
-m 256

Restart Memcached

$ sudo systemctl restart memcached

Installing Composer and Node/NPM

This section covers the instructions to installing Composer and Node.js and NPM on your server.

Install Composer

$ sudo apt install curl git unzip zip
$ cd ~
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
$ php -r "unlink('composer-setup.php');"

Composer will be installed at /usr/local/bin/composer and you can test the installation as follows:

$ /usr/local/bin/composer -V
Composer version 2.1.6 2021-08-19 17:11:08

If you get an error output stating Installer corrupt, it means the version of composer has been updated and you'll need a new hash. Visit the official getcomposer.org download page to get the latest hash_file string and/or install via that method.

Install Node & NPM

Debian 10 comes with Node 10 and the following instructions will install Node 12 and NPM 6 by installing from source. If you would like to install Node 14 simply replace setup_12.x in the command below with setup_14.x. If that's not high enough and you'd like Node 15, replace the command with setup_15.x instead.

$ cd ~
$ curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
$ sudo bash nodesource_setup.sh
$ sudo apt-get install -y nodejs

Test the installation:

$ node -v
v12.22.5
$ npm -v
6.14.14

Install Yarn

To install the Yarn package manager, run:

$ curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
$ echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt-get update && sudo apt-get install yarn

You can test it out with:

$ yarn -v
1.22.5

Optional

Optionally, you can install the Debian build-essential packages as certain node packages require the source code of certain packages when building them at runtime. You probably won't need to do this but I've included it here for those who think they will need it moving forward.

$ sudo apt install build-essential
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment