Skip to content

Instantly share code, notes, and snippets.

@justinhartman
Last active July 11, 2023 08:23
Show Gist options
  • Save justinhartman/a35839f06a03a1929aff27fdf15181d6 to your computer and use it in GitHub Desktop.
Save justinhartman/a35839f06a03a1929aff27fdf15181d6 to your computer and use it in GitHub Desktop.
Setup Azure Ubuntu 18.04 LEMP VM

How To Install Nginx, MySQL, PHP, SFTP on an Ubuntu Azure Virtual Machine

This series of documents will configure and setup a Nginx, MySQL, and PHP (LEMP) server on a basic Standard B1s (1 vcpus, 1 GiB memory) Ubuntu 16.04 or 18.04 LTS Virtual Machine on Microsoft Azure.

This will also install other useful packages and configurations for SFTP and a fully automated SSL service using certbot for Let's Encrypt.

The B1s is Azure's entry level Linux VM and only comes with 1 GiB memory so you need to optimise the install and configuration to maximise the server to your advantage.

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

  1. General Setup
  2. Creating swap on Azure Ubuntu VM
  3. Install Nginx, MySQL, and PHP (LEMP)
  4. Create a new nginx vhost
  5. Setup Let's Encrypt SSL certificates
  6. Setup SFTP server
  7. Download and Configure phpMyAdmin

These instructions assume you have:

  1. Created a VM instance on Azure by going to this link.
  2. Selected either Ubuntu Server 18.04 LTS - Gen 1 or Ubuntu Server 16.04 LTS - Gen 1 for your server Image.
  3. Selected a Standard_B1s (or higher) Size for your VM.
  4. Used SSH public key for your authentication method to the VM.
  5. Enabled ports 22, 80, and 443 under the Select inbound ports setting.

General Setup

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

$ sudo locale-gen en_GB.UTF-8 # fix locales
$ sudo cp /usr/share/zoneinfo/Africa/Johannesburg /etc/localtime # set time

SFTP

$ sudo adduser $(whoami) www-data # add yourself so you can SFTP to overwrite files in /var/www
$ sudo chmod -R g+w /var/www # ensure group can write to www

Creating swap on Azure Ubuntu VM

Run these commands to create a 3GB swap on an Azure Ubuntu VM.

$ sudo swapon --show # must output nothing so the below can work
$ sudo fallocate -l 3G /swapfile
$ sudo dd if=/dev/zero of=/swapfile bs=1024 count=3145728
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ sudo nano /etc/fstab

Add this to /etc/fstab.

/swapfile swap swap defaults 0 0

Check the swap has been added.

$ sudo swapon --show
NAME      TYPE SIZE   USED PRIO
/swapfile file   3G     0M   -2
$ sudo free -h
              total        used        free      shared  buff/cache   available
Mem:           889M        425M         75M         74M        389M        241M
Swap:          3.0G          0M        3.0G

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

Step 2 - Install MySQL

$ sudo apt install mysql-server mysql-client
$ sudo systemctl start mysql
$ sudo systemctl enable mysql
$ 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

$ sudo apt-get update
$ sudo apt -y install software-properties-common
$ sudo add-apt-repository ppa:ondrej/php
$ sudo apt update
$ sudo apt -y install php7.4-{fpm,cli,apc,bcmath,bz2,curl,gd,imagick,intl,memcached,mbstring,mysql,xml,zip}
$ sudo systemctl start php7.4-fpm
$ sudo systemctl enable php7.4-fpm

Configure php.ini

$ 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=Africa/Johannesburg
[opcache]
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.validate_timestamps=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 php7.4-fpm
$ sudo systemctl reload nginx

Configure php-fpm.conf

$ sudo nano /etc/php/7.4/fpm/php-fpm.conf
emergency_restart_threshold 10
emergency_restart_interval 1m
process_control_timeout 10s

Configure nginx

$ sudo nano /etc/nginx/nginx.conf

Change these values:

client_max_body_size 128M;
keepalive_timeout 2;
server_tokens off;

Edit default host to enable PHP-FPM.

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

Change these values:

index index.php index.html index.htm index.nginx-debian.html;
location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-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

Create a new nginx vhost

$ sudo nano /etc/nginx/sites-available/example.com
server {
        listen 80;
        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;
        }

        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 [email protected] -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

Configure Certbot to reload NGINX when it successfully renews certificates:

$ echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
$ sudo chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
$ sudo nginx -t && sudo systemctl reload nginx

Setup SFTP server

This uses public/private SSH key for authentication. Your user will only be able to write files in /var/www.

$ sudo apt install ssh
$ sudo addgroup sftp
$ sudo useradd -m FTPUSER -g sftp
$ sudo passwd FTPUSER
$ sudo adduser FTPUSER www-data # add user to group they can write to www folder
$ sudo chmod -R g+w /var/www # set group write permissions
$ sudo mkdir -p /home/FTPUSER/.ssh
$ sudo nano /home/FTPUSER/.ssh/authorized_keys # copy public key from computer and paste here
$ sudo chmod -R go= /home/FTPUSER/.ssh
$ sudo chown -R FTPUSER:sftp /home/FTPUSER/.ssh
$ sudo nano /etc/ssh/sshd_config
PubkeyAuthentication    yes
Match group sftp
    X11Forwarding no
    AllowAgentForwarding no
    AllowTcpForwarding no
    ForceCommand internal-sftp
$ sudo service ssh restart

Test it out using your private key.

$ sftp -i ~/.ssh/id_rsa [email protected]
Connected to 40.88.136.129.
sftp> exit

TODO: figure out why ChrootDirectory /var/www/ in /etc/ssh/sshd_config creates a broken_pipe issue when connecting to the SFTP server. This has been disabled until I can solve this! 🙀

Download and Configure phpMyAdmin

$ cd /var/www/html
$ sudo -u www-data mkdir phpmyadmin
$ sudo -u www-data wget https://files.phpmyadmin.net/phpMyAdmin/4.9.5/phpMyAdmin-4.9.5-english.tar.gz
$ sudo -u www-data tar -zxf phpMyAdmin-4.9.5-english.tar.gz -C phpmyadmin --strip-components 1
$ sudo -u www-data rm phpMyAdmin-4.9.5-english.tar.gz
  1. Open phpMyAdmin in a browser and log in as root.
  2. Create a database called phpmyadmin
  3. Create a user called pma and set the host to localhost.
  4. Open the SQL dump file in /var/www/html/phpmyadmin/create_tables.sql.
  5. In phpMyAdmin, select the phpmyadmin database and click on the SQL tab.
  6. Copy/paste the entire text from create_tables.sql into the text box, and run the query.
  7. Open the config.inc.php file in the phpMyAdmin install directory, and add the following.
/* Blowfish needed for cookie based authentication. MUST to be 32 chars long. */
$cfg['blowfish_secret'] = '12345678901234567890123456789012';
$cfg['DefaultConnectionCollation'] = 'utf8mb4_unicode_ci';
/* Servers configuration */
$i = 0;
/* First server */
$i++;
/* Authentication type */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
/* Server parameters */
$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['compress'] = false;
$cfg['Servers'][$i]['AllowNoPassword'] = false;
/* Hide system dbs */
$cfg['Servers'][$i]['hide_db'] = '^(information_schema|mysql|performance_schema|sys)$';
/* phpMyAdmin configuration storage settings. */
$cfg['Servers'][$i]['controluser'] = 'phpmyadmin';
$cfg['Servers'][$i]['controlpass'] = 'ADD_DB_PASSWORD';
/* phpMyAdmin storage database and tables */
$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
$cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
$cfg['Servers'][$i]['relation'] = 'pma__relation';
$cfg['Servers'][$i]['table_info'] = 'pma__table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma__column_info';
$cfg['Servers'][$i]['history'] = 'pma__history';
$cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
$cfg['Servers'][$i]['tracking'] = 'pma__tracking';
$cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
$cfg['Servers'][$i]['recent'] = 'pma__recent';
$cfg['Servers'][$i]['favorite'] = 'pma__favorite';
$cfg['Servers'][$i]['users'] = 'pma__users';
$cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
$cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
$cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
$cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
$cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
$cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';
/* Directories for saving/loading files from server */
$cfg['UploadDir'] = '';
$cfg['SaveDir'] = '';
/* General config */
$cfg['RowActionType'] = 'icons';
$cfg['ShowAll'] = true;
$cfg['MaxRows'] = 50;
$cfg['QueryHistoryDB'] = true;
$cfg['QueryHistoryMax'] = 100;
$cfg['SendErrorReports'] = 'never';
$cfg['Console']['DarkTheme'] = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment