Local LEMP development [Tutorial]

EOS LEMP Web development platform

To make things easier for myself as a PHP web developer I set up localhost to be my web server and develop locally before uploading files to the necessary server. This works very effectively using the LEMP stack ( Linux NGINX MariaDB PHP ), which in my experience is faster than LAMP / Apache.

Disclaimer:
You need to be at least slightly familiar with PHP, and the command line in order to work this. In the examples below I will use “mydomain.local” as the example domain to develop for; change it to yours. Likewise with “username”.
This is not the most secure way of setting up a webserver; it’s not supposed to be, it’s supposed to be the easiest to work with in offline development.
I welcome any critique and corrections to this guide, but I have found this has worked for me running under Antergos/EOS for the last 2+ years and similar configuration on Mint/Debian for the last 10 years.

Concept one:
Your /etc/hosts file controls how your machine sees the internet and domain names. You are going to develop for a specific domain name, so the best way to control this is by modifying the /etc/hosts file.
Add a line to it to control access to the domain name mydomain.local like this:
sudo nano /etc/hosts
add:
127.0.0.1 mydomain.local
ctrl-x, y, enter

Concept two:
In almost every other tutorial for local development they will suggest that you are keeping the website files in /var/www or similar. This has some drawbacks that are significant, in that it is outside your easily backed-up /home folders. Unfortunately I have not managed to get MariaDB to be happy about moving it’s DB files, so you need to have a database backup plan.

Concept three:
Updates to systemd have historically borked my methods here, so lookout for that as an answer if things stop working for you. I list one such fix below.

NB: // below denotes a comment

Recipe

// updates system first
sudo pacman -Syu

// install nginx
sudo pacman -S nginx-mainline

// enable the nginx service
sudo systemctl enable nginx

// stop the service if it is already running, because we’re going to change things…
sudo systemctl stop nginx

// install mariaDB
sudo pacman -S mariadb

// initialize the database
sudo mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

// enable and start mariadb
sudo systemctl enable mariadb && systemctl start mariadb

// set a root user password for mariadb, answer Y to all
mysql_secure_installation

// login to mariadb with the root password just set, type quit to exit
mysql -u root -p

// install php
sudo pacman -S php php-fpm

// enable php
sudo systemctl enable php-fpm

// now we will set our custom directories for working files, working within our home folder gives us all rights
mkdir ~/htdocs
mkdir ~/htdocs/sites
mkdir ~/htdocs/database
mkdir ~/htdocs/backups
mkdir ~/htdocs/errorpages
mkdir ~/htdocs/logs
mkdir ~/htdocs/sites-enabled
mkdir ~/htdocs/tmp

// phpmyadmin: it is possible to use the package manager to install phpmyadmin. However I prefer to install it separately like so:
cd ~/htdocs/database
wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
tar -zxvf phpMy*
ls -la php*
// see what the directory retrieved from the tar.gz is called, then move it to be pma/, so the line below is only example!
mv phpMyAdmin-5.0.2-all-languages/ pma/
cd ~/htdocs/sites-enabled
nano pma.local

// Enter the following between the hashed lines, taking care to change the username to yours

##################################################################################

server {

	root /home/username/htdocs/database/pma;

	index index.php;
	access_log /home/username/htdocs/logs/pma.local.access.log;
	error_log /home/username/htdocs/logs/pma.local.error.log;
	server_name pma.local;
	client_max_body_size 256M;
	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}
	
	location ~ \.php$ {
    try_files $uri $document_root$fastcgi_script_name =404;
    fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_read_timeout 180000;
    include fastcgi.conf;

    # prevention for httpoxy vulnerability: https://httpoxy.org/
    fastcgi_param HTTP_PROXY "";
}
	location ~ /\.ht {
		deny all;
	}
}

##################################################################################

ctrl-x, y, enter

// add pma.local to the hosts as per Concept One above
sudo nano /etc/hosts
// add:
127.0.0.1 pma.local
ctrl-x, y, enter

// now we can modify nginx config files to serve from our directories

sudo nano etc/nginx/nginx.conf

// Enter the following between the hashed lines, taking care to change the username to yours

##################################################################################

user username; # <--- this sets nginx up to run as you!
worker_processes  8;

error_log  /home/username/htdocs/logs/nginx-error.log info;

events {
    worker_connections  1024;
}


http {
	types_hash_max_size 1024;
    include       mime.types;
    default_type  application/octet-stream;

    access_log  /home/username/htdocs/logs/nginx-access.log;

    sendfile        on;

    keepalive_timeout  65;

    gzip  on;

    server { # <-- the default localhost server accessed through http://localhost
        listen       80; # <-- what TCP/IP port nginx will listen on
        server_name  localhost; <-- aka 127.0.0.1

        location / {
            root   /home/username/htdocs/sites/;  # <-- default root. Each development site should own another directory under this
            index  index.php index.html index.htm;  # <-- the filenames that nginx looks for as the default for a directory
            
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   404 500 502 503 504  /50x.html;
        location = /50x.html {
            root   /home/username/htdocs/errorpages/;
        }

		location ~ \.php$ {
            root           /home/username/htdocs/sites/;
            fastcgi_pass   unix:/run/php-fpm/php-fpm.sock;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
		}
	
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server  <-- example syntax only; there's not much point in using SSL connections locally
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
    
include sites-enabled/*;
include /home/username/htdocs/sites-enabled/*; # <-- we can easily create new site definitions in that folder and nginx will pick them up

##################################################################################
ctrl-x, y, enter

// make new site definition for nginx for our example domain
nano /home/username/htdocs/sites-enabled/mydomain.local
##################################################################################

server {
        server_name mydomain.local;
        root /home/username/htdocs/sites/mydomain; # <-- where your site files live
		client_max_body_size 512M;
		access_log off;
		error_log  /home/username/htdocs/logs/mydomain.local-error.log error; # <-- where your error logs go
		index index.php;
        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }
 
        # Very rarely should these ever be accessed outside of your lan
        location ~* \.(txt|log)$ {
                allow 127.0.0.1;
                deny all;
        }
 
        location ~ \..*/.*\.php$ {
                return 403;
        }
	location ~ \.php$ {
		try_files $uri $document_root$fastcgi_script_name =404;
		fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi.conf;

    # prevention for httpoxy vulnerability: https://httpoxy.org/
		fastcgi_param HTTP_PROXY "";
		send_timeout 86400; # <-- all of these settings are set high because there is no balancing required on a local environment
		fastcgi_buffers 16 16k; 
		fastcgi_buffer_size 32k;
		proxy_connect_timeout  600m;
		proxy_send_timeout  600m;
		proxy_read_timeout  600m;
		fastcgi_send_timeout 600m;
		fastcgi_read_timeout 600m;
	}
       
}

##################################################################################
ctrl-x, y, enter

// now we work on PHP configuration

sudo nano /etc/php/php-fpm.d/www.conf

// Enter the following between the hashed lines, taking care to change the username to yours

##################################################################################

error_log = /home/username/htdocs/logs/php-fpm.log
[www]

user = username
group = username


listen = /run/php-fpm/php-fpm.sock


listen.owner = username
listen.group = username

pm = dynamic


pm.max_children = 8

pm.start_servers = 2

pm.min_spare_servers = 1

pm.max_spare_servers = 3


rlimit_core = unlimited

##################################################################################
ctrl-x, y, enter

// the php.ini file controls most of the environment for php’s engine, but requires tuning for your needs. Of particular importance are the Dynamic Extensions
// mine pays attention to the following switches:

sudo nano /etc/php/php.ini
##################################################################################

;extension=bcmath
;extension=bz2
;extension=calendar
extension=curl
;extension=dba
;extension=enchant
;extension=exif
extension=ftp
;extension=gd
;extension=gettext
;extension=gmp
;extension=iconv
;extension=imap
;extension=intl
;extension=sodium
;extension=ldap
extension=mysqli
;extension=odbc
;zend_extension=opcache
;extension=pdo_dblib
extension=pdo_mysql
;extension=pdo_odbc
;extension=pdo_pgsql
;extension=pdo_sqlite
;extension=pgsql
;extension=pspell
;extension=shmop
;extension=snmp
;extension=soap
;extension=sockets
extension=sqlite3
;extension=sysvmsg
;extension=sysvsem
;extension=sysvshm
;extension=tidy
extension=xmlrpc
;extension=xsl
extension=zip


upload_max_filesize = 256M 
memory_limit = 512M
max_input_time = -1
date.timezone = Pacific/Auckland
open_basedir = /srv/http/:/var/www/:/home/username/htdocs/:/tmp/:/var/tmp/:/var/cache/:/usr/share/pear/:/usr/share/webapps/:/etc/webapps/

##################################################################################
ctrl-x, y, enter

// put a test page in for php
nano /home/username/htdocs/sites/index.php
##################################################################################

<?php
phpinfo();
?>

##################################################################################
ctrl-x, y, enter

// start the engines
sudo systemctl start nginx
sudo systemctl start php-fpm

// If all is well, no errors displayed, then you can open a web browser and go to http://localhost to see the PHP info screen
// then start filling up /home/username/htdocs/sites/mydomain with your new PHP files, etc. usually starting with an index.php
// Also you can use PHPMyAdmin at http://pma.local
// If there are errors starting up then use:
journalctl -xe
// to determine what the problem is. There may be issues with permissions so you have to force ownership of a socket file, eg:
sudo chown username:username /run/php-fpm/php-fpm.sock

==============================================================================
//EOF

==============================================================================

FOSS IDE / Programming Tools:

These are very much a personal taste thing, so find what works for you.
Filezilla is an excellent way to connect to remote server via SFTP and push/pull files.
Geany is a lightweight GTK IDE for many languages, but handles PHP/HTML/CSS/Javascript with ease, and can be paired with Filezilla for remote file editing.
Komodo Edit or Komodo IDE are great lightweight editors for our purposes. The IDE version needs to be downloaded from their website with version 12, and this can work with Xdebug PHP debugger.
NetBeans 12 is big, slow, and powerful. But in my experience a bit temperamental with it’s debugger handling.
VS Code is Microsoft’s attempt to influence developers on Linux. Not for me; ymmv.
Atom is a lightweight text editor that annoys me with it’s layout. Ymmv.
Xdebug is the PHP debugger.

… there are many more, including Kate for KDE …

Links:

Nginx: https://wiki.archlinux.org/index.php/Nginx
MariaDB: https://wiki.archlinux.org/index.php/MariaDB
PHP: https://wiki.archlinux.org/index.php/PHP
PHPMyAdmin: https://wiki.archlinux.org/index.php/phpMyAdmin
https://bbs.archlinux.org/viewtopic.php?pid=1875352#p1875352 <-- Fix a problem where after PHP update files are no longer working from /home folders due to ProtectHome switch in systemd

7 Likes

Should be sudo nano /etc/nginx/nginx.conf

After a system reload I am working through this again, and find that nginx.conf needs to be:


user username;
worker_processes  8;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  /home/username/htdocs/logs/nginx-error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
	types_hash_max_size 1024;
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /home/username/htdocs/logs/localhost-access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        root   /home/username/htdocs/sites;
        location / {

            index  index.php index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        location ~ \.php$ {
        # 404
        try_files $fastcgi_script_name =404;

        # default fastcgi_params
        include fastcgi_params;

        # fastcgi settings
        fastcgi_pass			unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index			index.php;
        fastcgi_buffers			8 16k;
        fastcgi_buffer_size		32k;

        # fastcgi params
        fastcgi_param DOCUMENT_ROOT	$realpath_root;
        fastcgi_param SCRIPT_FILENAME	$realpath_root$fastcgi_script_name;
        #fastcgi_param PHP_ADMIN_VALUE	"open_basedir=$base/:/usr/lib/php/:/tmp/";
    }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


include sites-enabled/*;
include /home/username/htdocs/sites-enabled/*; # <-- we can easily create new site definitions in that folder and nginx will pick them up
}
}

Hi onyxnz
Thank you for making this tutorial.
Please add the missing } to the end of the /etc/nginx/nginx.conf file as otherwise, the nginx server won’t start. :slight_smile:

One more:
sudo nano /etc/php/php.ini needs “;extension=iconv” un-commented as well.

1 Like

Good point; for those needing to, iconv is used to do multilingual scripts.
https://www.php.net/manual/en/intro.iconv.php
I have never needed that, YMMV.