Production LAMP system: Lighttpd + PHP5 + MySQL

From Alpine Linux
Revision as of 15:18, 4 November 2020 by Mckaygerhard (talk | contribs) (note about FAM)
Jump to: navigation, search

In production web, LAMP means Linux + Apache + Mysql + Php installed and integrated, but today the "A" of apache are more used as Nginx or Lighttpd, and the "M" of MySQL are more used as Mariadb, the LAMP focused documents are:

Introduction and why php5

If you are not following PHP closely or you are new to PHP programming, you should know that before PHP 7, PHP 5.2 used to be the most used version up to 5.6 as mayor version. Obviously, with the release of PHP 7, people started to compare it with its previous version. The problem comes with some very specific software that for rare or practical reasons performs well on php5 only and not in php7 or needs mayor rewrite for.

PHP7 vs PHP5

These are the main reasons that mayor software cannot migrated easyle to php7:

Declaring The Return Type

In PHP 5, the programmer cannot define the return type of a function or method. Fortunately, PHP 7 allows programmers to declare the return type of the functions as per the expected return value, there are four different return types available – bool, int, string, and float.

Error Handling and 64-bit Support

If you understand the difference between error and exception, you know that it is highly uneasy to handle fatal errors in PHP 5. PHP 7 has eased the process as it has replaced several major errors with exceptions that can be handled effortlessly. This has been achieved with the introduction of the new Engine Exception objects. As you might be aware that PHP 5 does not support 64-bit integer or large files but the scenario has changed in PHP 7. PHP 7 has 64-bit support due to which you will be able to use native 64-bit integers as well as large files and hence, you can run applications flawlessly on 64-bit system architectures.

Anonymous Class

A mayor stupid change but great object oriented improvement. One of the major additions to PHP 7 that is not present in PHP 5 is the anonymous class. An anonymous class is used to speed up the execution time. It is suitable when you do not need to execute a class more than once and you do not need to document it in the project document.

1. The web server part: Lighttpd

lighttpd is a simple, standards-compliant, secure, and flexible web server, Nginx are the most use due are administrable by ISP panel's software, but lighttpd performs better always. Nginx could not process fast-cgi programs. for more complete lighttpd consult the Production Web server: Lighttpd wiki page.

Lighttpd Installation

Production environment only will handle need packages.. so no doc or manages allowed:

  1. run apk for need packages

apk add lighttpd gamin

Lighttpd pre php configuration

  1. make the htdos public web root directories
  2. change default port to production one, http are used with 80
  3. use FAM style (gamin) file alteration monitor, increases performance ONLY IN OLDER Alpine Releases less than 3.8
  4. use linux event handler, increases performance due Alpine are linux only
  5. added the service to the default runlevel, not to boot, because need networking activated
  6. started the web server service
  7. Enable the mod_status at the config files
  8. change path in the config file, we are using security by obfuscation
  9. restart the service to see changes at the browser

mkdir -p /var/www/localhost/htdocs/stats /var/log/lighttpd /var/lib/lighttpd

sed -i -r 's#\#.*server.port.*=.*#server.port          = 80#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#\#.*server.event-handler = "linux-sysepoll".*#server.event-handler = "linux-sysepoll"#g' /etc/lighttpd/lighttpd.conf

chown -R lighttpd:lighttpd /var/www/localhost/

chown -R lighttpd:lighttpd /var/lib/lighttpd

chown -R lighttpd:lighttpd /var/log/lighttpd

rc-update add lighttpd default

rc-service lighttpd restart

echo "it works" > /var/www/localhost/htdocs/index.html

sed -i -r 's#\#.*mod_status.*,.*#    "mod_status",#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*status.status-url.*=.*#status.status-url  = "/stats/server-status"#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*status.config-url.*=.*#status.config-url  = "/stats/server-config"#g' /etc/lighttpd/lighttpd.conf

rc-service lighttpd restart

For testing open a browser and go to http://<webserveripaddres> and you will see "it works". The "webserveripaddres" are the ip address of your setup/server machine.

There's a problem in Alpine linux, FAM (gamin) are activated as a lighttpd only service, that's make sense in dockers but in servers could be a problem if FAM (gamin) are also need for others services at the same time.

OPTIONAL: alpine packagers are a mess, removed fam on recents, soolder releases of alpine can use compiled fam packages with sed -i -r 's#.*server.stat-cache-engine.*=.*# server.stat-cache-engine = "fam"#g' /etc/lighttpd/lighttpd.conf

2. The php scripting part: PHP fpm

In Alpine there's two main language for programming dynamic web pages: PHP and LUA. Alpine are minimalist so not all PHP packages are need in most cases, both repositories must be enabled (main and community), here are explained the most common used in production, for PHP at development please watch the Alpine_newbie_developer wiki page.

PHP Installation

Since version v3.5, PHP 5 is available along with PHP 5.6 coexisting together, until version v3.9 where the latter was removed. So from Alpine 3.5 to Alpine 3.8 we can use php5 as 5.6 version, if you need PHP5 still could use it, that will be cover in the special Production LAMP system: Lighttpd + PHP + MySQL wiki page for older Alpine systems and some specific php software.

apk add php5 php5-bcmath php5-bz2 php5-ctype php5-curl php5-dba php5-dom php5-enchant php5-exif php5-fpm php5-gd php5-gettext php5-gmp php5-iconv php5-imap php5-intl php5-json php5-mbstring php5-opcache php5-openssl php5-phar php5-posix php5-pspell php5-recode php5-session php5-simplexml php5-sockets php5-sysvmsg php5-sysvsem php5-sysvshm php5-tidy php5-tokenizer php5-xml php5-xmlreader php5-xmlrpc php5-xmlwriter php5-xsl php5-zip php5-sqlite3

Note: The below packages are only for specific situations.. only install when need (specially php-pear one), by example, cacti and cacti-php5 are in edge that depends on php5, but you must only install from edge only the cacti package, all the depends must be previously installed from stable. (that was in the past, today cacti depends only on php7 packages)
apk add php5-pgsql php5-mysqli php5-mysqlnd php5-snmp php5-soap php5-ldap php5-pcntl php5-pear php5-wddx php5-cgi php5-pdo php5-snmp php5-tokenizer 
Note: The below packages are only for databases access using php in specific ways.. only install when need (specially php--pdo ones), by example, cacti and cacti-php5 are in edge that depends on php5-mysqli, but you must only install from edge only the cacti package, all the depends like php5 and php5-mysqli must be previously installed from stable. (that was in the past, today cacti depends only on php7 packages)
apk add php5-dba php5-sqlite3 php5-mysqli php5-mysqlnd php5-pgsql php5-pdo_dblib php5-pdo_odbc php5-pdo_pgsql php5-pdo_sqlite 

A special case it's the php5-odbc, unless the others, that are able php to connect to only specific database, unixodbc are a universal way to do so, the most important difference are that by example, php5-mysqli package has better functions to manage data into the php software usage.

PHP Global Configuration

  1. Use fix.pathinfo
  2. Set safe mode off
  3. Dont expose php code if something fails
  4. Set amount of memory limit for execution to 536Mb (most servers are minimal of 1Gb of RAM)
  5. So then set upload size to 128Mb as maximun.
  6. Set then POST max size to 256Mb based on the upload max size limit.
  7. Turn on the url open method
  8. Set default charset to UTF-8 more compatible
  9. Increase the execution time and the input time for.
sed -i -r 's|.*cgi.fix_pathinfo=.*|cgi.fix_pathinfo=1|g' /etc/php*/php.ini
sed -i -r 's#.*safe_mode =.*#safe_mode = Off#g' /etc/php*/php.ini
sed -i -r 's#.*expose_php =.*#expose_php = Off#g' /etc/php*/php.ini
sed -i -r 's#memory_limit =.*#memory_limit = 536M#g' /etc/php*/php.ini
sed -i -r 's#upload_max_filesize =.*#upload_max_filesize = 128M#g' /etc/php*/php.ini
sed -i -r 's#post_max_size =.*#post_max_size = 256M#g' /etc/php*/php.ini
sed -i -r 's#^file_uploads =.*#file_uploads = On#g' /etc/php*/php.ini
sed -i -r 's#^max_file_uploads =.*#max_file_uploads = 12#g' /etc/php*/php.ini
sed -i -r 's#^allow_url_fopen = .*#allow_url_fopen = On#g' /etc/php*/php.ini
sed -i -r 's#^.default_charset =.*#default_charset = "UTF-8"#g' /etc/php*/php.ini
sed -i -r 's#^.max_execution_time =.*#max_execution_time = 150#g' /etc/php*/php.ini
sed -i -r 's#^max_input_time =.*#max_input_time = 90#g' /etc/php*/php.ini

PHP-FPM Configuration

  1. Create directory for php socket and pid files, MUST BE EQUAL to openrc defined!
  2. Set into configuration file the socket path, MUST BE EQUAL to openrc defined!
  3. Set into configuration file the pid file path, MUST BE EQUAL to openrc defined!

mkdir -p /var/run/php-fpm5/

chown lighttpd:root /var/run/php-fpm5

sed -i -r 's|^.*listen =.*|listen = /run/php-fpm5/php5-fpm.sock|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^pid =.*|pid = /run/php-fpm5/|g' /etc/php*/php-fpm.conf

sed -i -r 's|^.*listen.mode =.*|listen.mode = 0640|g' /etc/php*/php-fpm.d/www.conf

rc-update add php-fpm5 default

service php-fpm5 restart

The PHP-FPM defined a master process with some pool of process for each service requests, by default there's only one pool of processes, the www pool process.

Default values are good for starting, but later will need tuning, the best it's static one but need test and set until get right configuration.

Lighttpd + PHP-FPM

The web server comes to and very unmaintained config file so we must handle all the required settings:

  1. enable the mod_alias at the config file, due need of a specific path for cgi files into security
  2. be sure and disable the fastcgi-php module by cgi only
  3. and then enable the fastcgi-php-fpm specific module then
  4. write a much much better approach of the php handler in the local server using the socket
  5. configure the php to use also the socket too for direct connection locally
  6. restart the service to see changes at the browser

mkdir -p /var/www/localhost/cgi-bin

sed -i -r 's#\#.*mod_alias.*,.*#    "mod_alias",#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_cgi.conf".*#   include "mod_cgi.conf"#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_fastcgi.conf".*#\#   include "mod_fastcgi.conf"#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_fastcgi_fpm.conf".*#   include "mod_fastcgi_fpm.conf"#g' /etc/lighttpd/lighttpd.conf

cat > /etc/lighttpd/mod_fastcgi_fpm.conf << EOF
server.modules += ( "mod_fastcgi" )
index-file.names += ( "index.php" )
fastcgi.server = (
    ".php" => (
        "socket"                => "/var/run/php-fpm5/php5-fpm.sock",
        "broken-scriptfilename" => "enable"

sed -i -r 's|^.*listen =.*|listen = /var/run/php-fpm5/php5-fpm.sock|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.*listen.owner = .*|listen.owner = lighttpd|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.* = .*| = lighttpd|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.*listen.mode = .*|listen.mode = 0660|g' /etc/php*/php-fpm.d/www.conf

rc-service php-fpm5 restart

rc-service lighttpd restart

echo "<?php echo phpinfo(); ?>" > /var/www/localhost/htdocs/info.php

For testing open a browser and go to http://<webserveripaddres>/info.php and you will see only the minimal info due in production there's no need for too much information to crackers. The "webserveripaddres" are the ip address of your setup/server machine.

After that, all the files with php will be proceses faster than used a host based, also under the /var/www/localhost/cgi-bin directory will be showed as http://localhost/cgi-bin/ path.

Multiple PHP-FPM cluster

As we said, in FPM it is managed by process pools, but the connection can be over the network or over a direct n socket, the configuration for a powerful server of average requests is with socket and localhost, but for high availability it is required CAT6 category 6 network connections of 1000Mbps and php-fpm by network connections in roundrobin mode.

For php fpm pool will be on a specific machine and the web server(s) will simply connect to these machines with php to serve the php pages, the result is that we have a cluster of lighttpd web servers against other php-fpm process clusters, the php project can be the same code on all web servers and connected to a single database.

At the Linux console the change are, by example two machines and both have php and lighttpd, so then in each one will setup the php of the other:

mkdir -p /var/www/localhost/cgi-bin

sed -i -r 's#\#.*mod_alias.*,.*#    "mod_alias",#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_cgi.conf".*#   include "mod_cgi.conf"#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_fastcgi.conf".*#\#   include "mod_fastcgi.conf"#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_fastcgi_fpm.conf".*#   include "mod_fastcgi_fpm.conf"#g' /etc/lighttpd/lighttpd.conf

cat > /etc/lighttpd/mod_fastcgi_fpm.conf << EOF
server.modules += ( "mod_fastcgi" )
index-file.names += ( "index.php" )
fastcgi.server = ( ".php" => 
    ( "host" => "",
      "port" => 9000
    ( "host" => "",
      "port" => 9000 )

sed -i -r 's|^.*listen =.*|listen = 9000|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.*listen.owner = .*|listen.owner = lighttpd|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.* = .*| = lighttpd|g' /etc/php*/php-fpm.d/www.conf

sed -i -r 's|^.*listen.mode = .*|listen.mode = 0660|g' /etc/php*/php-fpm.d/www.conf

rc-service php-fpm5 restart

rc-service lighttpd restart

echo "<?php echo phpinfo(); ?>" > /var/www/localhost/htdocs/info.php

3. The DBMS part: mysql/mariadb

Alpine Linux has dummy counterparts packages for those that are not close to that change from mysql to mariadb naming packages. This instalation are for older systems, for most newer please refers to the MariaDB wiki page.


Take in consideration that the user mysql was created during instalation of packages, in the initialization section two users will be created in database init: root and mysql, and in that point only if are in their respective system accounts, will be able to connect to the database service.

apk add mysql mysql-client


The datadir are located to /var/lib/mysql must be owned by the mysql user and group. You can modify this behavior but must edit the service file at /etc/init.d directory. Also, you need to set datadir=<YOUR_DATADIR> under section [mysqld] at the config file.

  1. Initialize the main mysql database, and the data dir as standardized to /var/lib/mysql by the rc script
  2. Then initialize the service, root account and socket connection are enabled without password at this point
  3. Setup the root account by asignes a proper password, this are purely paranoid. due next step already do that!
  4. Setup and init the installation by running the mysql_secure_installation
  5. Setup permissions for manage others users and databases
  6. Run the mysql_secure_installation script and answer the questions (see section below)

mysql_install_db --user=mysql --datadir=/var/lib/mysql

rc-service mariadb start

mysqladmin -u root password toor


  1. Enter current password for root (enter for none): must be provided due we already set previously. correct respond are OK, successfully used password, moving on...
  2. Switch to unix_socket authentication [Y/n] this are not the case and must be disabled, so answer NO, and response will be ... skipping.
  3. Change the root password? [Y/n] Just press "n" only if you provided a good password, otherwise just change it!
  4. Remove anonymous users? [Y/n] In any case, production system must remove it, so answer Y and proper respond mus be ... Success!.
  5. Disallow root login remotely? [Y/n] For sure answer Y and proper respond mus be ... Success!.
  6. Remove test database and access to it? [Y/n] Should be removed, so answer Y and proper respond mus be ... Success!.
  7. Reload privilege tables now? [Y/n] Aanswer Y and proper respond mus be ... Success!.

After reponse all the questions.. restart the service with rs-service mariadb restart


Newer system Alpine packages can set in independent files in any case those commands always works and where are not apply just will ignore the output, for more info about that watch the MariaDB Configuration files section of the MariaDB wiki page.

  • On older Alpine system must set config files for MAX ALLOWED PACKETS to minimun proper amount:
  • Only allow local connections on cases where there's only one server or no expected to connect from others:
  • Set default charset to UTF8MB4
  • Added the service to start process but not at boot process due needs networking started.
  • Restart the service to apply changes.

sed -i "s|.*max_allowed_packet\s*=.*|max_allowed_packet = 100M|g" /etc/mysql/my.cnf
sed -i "s|.*max_allowed_packet\s*=.*|max_allowed_packet = 100M|g" /etc/my.cnf.d/mariadb-server.cnf

sed -i "s|.*bind-address\s*=.*|bind-address=|g" /etc/mysql/my.cnf
sed -i "s|.*bind-address\s*=.*|bind-address=|g" /etc/my.cnf.d/mariadb-server.cnf

cat > /etc/my.cnf.d/mariadb-server-default-charset.cnf << EOF
default-character-set = utf8mb4

collation_server = utf8mb4_unicode_ci
character_set_server = utf8mb4

default-character-set = utf8mb4

rc-service mariadb restart

rc-update add mariadb default

On upgrade cases: If are unable to run any mysql command after an upgrade, it's because MySQL cannot start try run MySQL in safemode with mysqld_safe --datadir=/var/lib/mysql/ command and then run the mysql_upgrade -u root -p script. For more information watch the MariaDB upgrading section of the MariaDB wiki page.

adminer: Web Frontend administration

Adminer are a simple and single tool, tons of times faster thant PhpMysqladmin that are great but have too much security issues and lot of complext settings, we must use a more single and simpel solution easy to manage and upgrade.

Take in consideration that this needs as requisite the previous sections of web server, php scripting and mysql/mariadb engine configured and running:

mkdir -p /var/www/webapps/adminer

wget -O /var/www/webapps/adminer/adminer-4.7.6.php

ln -s adminer-4.7.6.php /var/www/webapps/adminer/index.php

cat > /etc/lighttpd/mod_adminer.conf << EOF
# NOTE: this requires mod_alias
alias.url += (
     "/adminer/"	    =>	    "/var/www/webapps/adminer/"
$HTTP["url"] =~ "^/adminer/" {
    # disable directory listings
    dir-listing.activate = "disable"

sed -i -r 's#\#.*mod_alias.*,.*#    "mod_alias",#g' /etc/lighttpd/lighttpd.conf

sed -i -r 's#.*include "mod_cgi.conf".*#   include "mod_cgi.conf"#g' /etc/lighttpd/lighttpd.conf

checkssl="";checkssl=$(grep 'include "mod_adminer.conf' /etc/lighttpd/lighttpd.conf);[[ "$checkssl" != "" ]] && echo listo || sed -i -r 's#.*include "mod_cgi.conf".*#include "mod_cgi.conf"\ninclude "mod_adminer.conf"#g' /etc/lighttpd/lighttpd.conf

rc-service lighttpd restart

The administrator must visit with exact url http://<ipaddress>/adminer/index.php because two main reasons: there's no directory listing and there's no direct php index reference in web server, all of this due paranoiac settings.

See Also