Seguro que nada más instalar Rocky Linux 9, te has encontrado con que viene por defecto con PHP 8.0 como versión oficial de la distribución, aunque luego tienes PHP 8.1 y PHP 8.2 como module stream. Vale, la duda que te puede surgir es que el soporte oficial (EOL) de PHP 8.0 termino el 26 de noviembre de 2023. ¿Algún problema?
No. Es el soporte de PHP con mejoras y seguridad, pero RHEL sigue ofreciendo soporte hasta el final del ciclo de vida de RHEL 9 (distribución en la que se basa Rocky Linux 9): si hay problemas de seguridad, RHEL los va a solucionar. Estás cubierto. Pero el problema surge si necesitas nuevas funciones que van surgiendo en las nuevas versiones de PHP, y quieres instalarlas en tu servidor.
Ahora mismo tienes PHP 8.1, PHP 8.2 y PHP 8.3:
¿Qué te voy a contar en este artículo? Cómo instalar de forma segura varias versiones de PHP sin borrar la versión original de RL9. En mi caso, he instalado PHP 8.2 porque WordPress todavía no da un soporte total a PHP 8.3, y sobre todo, porque muchos plugins te pueden dar problemas con esta última versión. Mejor esperar un poco a que maduren un poco las cosas. ¡Ojo! No he utilizado los módulos de Stream de Rocky Linux, sino que he tomado la ruta del repositorio de REMI, que es lo que recomiendan en el panel de control que uso en el servidor, Virtualmin.
Mi instalación:
- Rocky Linux 9.4
- Apache 2.4.57
- PHP 8.2 (en modo PHP-FPM de Remi Repo) junto a PHP 8.0 (de repo Rocky Linux)
- MariaDB: 10.5.22
- Servidor con 16 GB de RAM y 6 núcleos.
- Panel de control Virtualmin / Webmin
Antes de comenzar. En la página web de Remi, tienes una página que te ayuda en la configuración. Puedes probar a hacerlo como te dice allí.
- Te recomiendo que también te leas otras entradas sobre como hacer las cosas en Rocky Linux 9: Cómo configurar la memoria SWAP, Cómo quitamos el acceso root, Cómo instalar Rclone o Mejorando la seguridad y rendimiento del Kernel.
Instalando PHP 8.2 en Rocky Linux 9
Activamos repositorios
Tienes que tener varios repositorios activos. En mi caso, que anda más iniciar el servidor instale Virtualmin, ya me activaron CRB y EPEL (son totalmente seguros y no hay conflictos con paquetes de las repo oficiales y más comunes de Rocky Linux). Si no los tienes activos, puedes hacerlo así:
subscription-manager repos --enable codeready-builder-for-rhel-9-x86_64-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
Instalamos el repositorio de REMI
Este es el comando general (que recomiendan en Virtualmin), en el que básicamente vas a comprobar lo siguiente:
- Carga las variables de entorno del archivo /etc/os-release.
- Determina si el sistema es Fedora o una distribución de Red Hat Enterprise Linux (RHEL) como Rocky Linux, Alma Linux.
- Instala el repositorio remi-release correspondiente para la distribución.
- Limpia la caché de dnf.
. /etc/os-release && repo_dir=$([ "$ID" = "fedora" ] && echo "fedora" || echo "enterprise") && dnf -y install "https://rpms.remirepo.net/$repo_dir/remi-release-$(rpm -E %$ID).rpm" && dnf clean all
Si estás seguro de cuál es tu distribución, en este caso Rocky Linux 9, también puedes poner:
dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
Instalamos en paralelo una nueva versión de PHP
Ahora ya podemos instalar una nueva versión de PHP. Actualmente, puedes elegir, PHP 8.1, PHP 8.2 o PHP 8.3. Yo me he decantado por PHP 8.2, para no tener problemas de compatibilidad con plugins de WordPress que todavía no se hayan adaptado los cambios de código de las diferentes versiones.
Puedes instalar solo PHP 8.2 o elegir instalar al mismo tiempo todas las extensiones PHP que va a necesitar , por ejemplo WordPress para funcionar bien.
Te puede interesar: Cómo instalar PHP Composer en Rocky Linux
¿Solo quieres instalar PHP 8.2?
dnf install php82
¿Quieres saber cuantas extensiones hay disponibles en el repositorio de Remi?
dnf repoquery --repo=remi -q php82-php-*
¿Quieres instalar alguna de ellas de manera individual? (cambia nombre-extension por el nombre que has listado con el comando anterior)
dnf install php82-php-nombre-extension
¿Quieres instalar PHP 8.2 y las extensiones que necesitas al mismo tiempo?
dnf install php82-php-{cli,fpm,pdo,gd,mbstring,mysqlnd,opcache,curl,xml,zip}
¿Qué extensiones he instalado yo con PHP 8.2 para usar con WordPress?
php82 --modules
[PHP Modules]
bz2
calendar
Core
ctype
curl
date
dom
exif
fileinfo
filter
ftp
gd
gettext
hash
iconv
igbinary
imagick
intl
json
libxml
mbstring
memcache
memcached
msgpack
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
random
readline
Reflection
session
SimpleXML
soap
sockets
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
xsl
Zend OPcache
zip
zlib
[Zend Modules]
Zend OPcache
¿Otra manera de instalar nuevas extensiones en PHP en todas tus versiones en el sistema? No es estrictamente necesario, pero con estos comandos instala las mismas extensiones en todas las versiones PHP que tengas en tu sistema.
for php in $(scl list-collections 2>/dev/null | grep 'php' | sed 's/$/-php/') php; do for ext in curl intl; do sudo dnf -y install "${php}-${ext}"; done; done
for php in $(scl list-collections 2>/dev/null | grep 'php' | sed 's/$/-php/') php; do for ext in imagick memcache memcached intl; do sudo dnf -y install "${php}-${ext}"; done; done
En el primer comando he instalado Curl e intl. En el segundo comando he instalado imagick, memcache, memcached e intl.
Inciso. En mi caso, que tengo Virtualmin instalada en el servidor, tengo que decirle al sistema que hay nuevas versiones de PHP instaladas: System Settings > Re-Check Configuration y luego, en cada Virtual Server, decirle que use la nueva versión: Web Configuration > PHP Options. Allí puedes elegir la versión PHP para cada dominio y ver los módulos activos. Virtualmin se encarga de cambiar la configuración de Apache para que funcione con PHP 8.2. En tu caso es posible que lo tengas que hacer a mano. En https://docs.rockylinux.org/guides/web/php/ tienes una buena guía.
Optimizando la nueva versión de PHP
Aquí tienes que tener cuidado, ya que los archivos de configuración han cambiado de sitio. En tu configuración original de PHP con Rocky Linux, es decir, con PHP 8.0, tenías los archivos de configuración en /etc/php.ini, /etc/php-fpm.d/www.conf y en /etc/php-fpm.conf.
Ahora con PHP 8.2 en REMI, tienes las cosas en varios sitios: /etc/opt/remi/php82/php-fpm.conf, /etc/opt/remi/php82/php.ini y /etc/opt/remi/php82/php-fpm.d/www.conf
En /etc/opt/remi/php82/php.ini puedes mejorar la seguridad cambiando a estos valores:
display_errors = Off
display_startup_errors = Off
log_errors = On
expose_php = Off
allow_url_include = Off
disable_functions = show_source, system, passthru, exec, popen, dl, shell_exec
session.use_strict_mode = 1 #antes en 0
session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_httponly = 1 #sin valor
session.cookie_secure = 1 #comentada y sin valor
session.cookie_samesite = "Lax" #sin valor
En /etc/opt/remi/php82/php-fpm.conf puedes cambiar el tiempo que tarda en responder PHP en reiniciar FPM (para que no dejen de funcionar las páginas mucho tiempo):
; If this number of child processes exit with SIGSEGV or SIGBUS within the time
; interval set by emergency_restart_interval then FPM will restart. A value
; of '0' means 'Off'.
; Default Value: 0
emergency_restart_threshold = 5
; Interval of time used by emergency_restart_interval to determine when
; a graceful restart will be initiated. This can be useful to work around
; accidental corruptions in an accelerator's shared memory.
; Available Units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
emergency_restart_interval = 30s
; Time limit for child processes to wait for a reaction on signals from master.
; Available units: s(econds), m(inutes), h(ours), or d(ays)
; Default Unit: seconds
; Default Value: 0
process_control_timeout = 10s
En /etc/opt/remi/php82/php-fpm.d/www.conf puedes optimizar cuanta memoria le dedica el sistema a PHP-FPM:
; Note: This value is mandatory.
pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 150
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 25
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 20
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 50
; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to 'ondemand'
; Default Value: 10s
;pm.process_idle_timeout = 10s;
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
pm.max_requests = 1000
¿Cómo puedes optimizar estos valores en función de la memoria RAM disponible en tu servidor?
Tenemos que conocer cuanta memoria está usando cada proceso de PHP-FPM con este comando:
while true; do ps --no-headers -o "rss,cmd" -C php-fpm | grep "pool www" | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"Mb") }' >> avg_php_proc; sleep 60; done
Imagínate que te sale que cada proceso ocupa 120 MB de memoria, y tienes 8 GB de RAM en tu servidor, de los cuales 1 GB lo usa tu sistema, 1 GB Opcache. Te quedan 6 a repartir. Haces esta operación: 6 * 1024 / 120= 51. Tu servidor puede manejar unos 50 hilos por lo que puedes poner una configuración de este estilo:
pm = dynamic
pm.max_children = 50
pm.start_servers = 12
pm.min_spare_servers = 12
pm.max_spare_servers = 36
pm.max_requests = 500
con:
- pm.start_servers = 25% de max_children
- pm.min_spare_servers = 25% de max_children
- pm.max_spare_servers = 75% de max_children
Tienes que ir probando y ajustando. Fíjate muy bien en cuanta memoria usa MariaDB, Memcached o Apache. Puedes descargar ps_mem.py de GitHub para que te dé una visión aproximada del uso de la memoria en tu sistema.
¡Ojo! Si tienes varios dominios instalados en tu servidor, cada uno de ellos va a tener un pool PHP-FPM. Cada uno de ellos tendrá unos parámetros de configuración en /etc/opt/remi/php82/php-fpm.d/numero_de_pool.conf, por lo que los parámetros de pm.max_children los vas a tener que ajustar dominio a dominio, quedando la configuración /etc/opt/remi/php82/php-fpm.d/www.conf como general. En estas configuraciones, como regla general, tendrás que poner valores inferiores a lo que pongas en el archivo www.conf.
- Ahora solo te falta optimizar los parámetros de configuración de Apache en función de la RAM de tu servidor.
¿Quieres comprobar que parámetros finales tienes en cada configuración?
php-fpm -tt
Reiniciamos los procesos PHP-FPM
Solo tienes que reiniciar los procesos PHP-FPM para que todo se cargue en el sistema. No hace falta reiniciar Apache. Como tienes varios PHP en tu sistema, vas a atener que reiniciarlos todos:
# Reiniciar PHP-FPM
sudo systemctl restart php-fpm
# Reiniciar PHP 8.2-FPM
sudo systemctl restart php82-php-fpm
# Verificar el estado de PHP-FPM
sudo systemctl status php-fpm
# Verificar el estado de PHP 8.2-FPM
sudo systemctl status php82-php-fpm
Configuramos Opcache
Superimportante optimizar correctamente Opcache si estás usando WordPress. Vas a notar una diferencia abismal en el rendimiento de tu blog. Vas a tener que cambiar las configuraciones de PHP-FPM en el directorio de REMI para PHP 8.2.
Dato: para PHP 8.0 tienes el archivo de configuración en /etc/php.d/10-opcache.ini, para la nueva versión de REMI lo tienes en /etc/opt/remi/php82/php.d/10-opcache.ini (te marco en negrita las cosas más importantes a cambiar):
- Opcache.memory_consumption corresponde a la cantidad de memoria necesaria para opcache. Tienes que ir ajustándola hasta que el número de hits supere el 90, 95 %.
- Opcache.interned_strings_buffer la cantidad de cadenas para almacenar en caché.
- Opcache.max_accelerated_files está cerca del resultado del comando find ./ -iname «*.php»|wc -l.
; Enable Zend OPcache extension module
zend_extension=opcache
; Determines if Zend OPCache is enabled
opcache.enable=1
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
; The OPcache shared memory storage size.
opcache.memory_consumption=1024
; The amount of memory for interned strings in Mbytes.
opcache.interned_strings_buffer=48
; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 1000000 are allowed.
opcache.max_accelerated_files=40000
; The maximum percentage of "wasted" memory until a restart is scheduled.
opcache.max_wasted_percentage=5
;para acelerar las cosas en PHP8
opcache.jit=tracing
opcache.jit_buffer_size=100M
; When this directive is enabled, the OPcache appends the current working
; directory to the script key, thus eliminating possible collisions between
; files with the same name (basename). Disabling the directive improves
; performance, but may break existing applications.
opcache.use_cwd=1
; When disabled, you must reset the OPcache manually or restart the
; webserver for changes to the filesystem to take effect.
opcache.validate_timestamps=0
; How often (in seconds) to check file timestamps for changes to the shared
; memory storage allocation. ("1" means validate once per second, but only
; once per request. "0" means always validate)
opcache.revalidate_freq=60
; Enables or disables file search in include_path optimization
;opcache.revalidate_path=0
; If disabled, all PHPDoc comments are dropped from the code to reduce the
; size of the optimized code.
;opcache.save_comments=1
; If enabled, compilation warnings (including notices and deprecations) will
; be recorded and replayed each time a file is included. Otherwise, compilation
; warnings will only be emitted when the file is first cached.
;opcache.record_warnings=0
; Allow file existence override (file_exists, etc.) performance feature.
opcache.enable_file_override=0
; A bitmask, where each bit enables or disables the appropriate OPcache
; passes
;opcache.optimization_level=0x7FFFBFFF
; This hack should only be enabled to work around "Cannot redeclare class"
; errors.
;opcache.dups_fix=0
; The location of the OPcache blacklist file (wildcards allowed).
; Each OPcache blacklist file is a text file that holds the names of files
; that should not be accelerated.
opcache.blacklist_filename=/etc/opt/remi/php82/php.d/opcache*.blacklist
; Allows exclusion of large files from being cached. By default all files
; are cached.
;opcache.max_file_size=0
; Check the cache checksum each N requests.
; The default value of "0" means that the checks are disabled.
;opcache.consistency_checks=0
; How long to wait (in seconds) for a scheduled restart to begin if the cache
; is not being accessed.
;opcache.force_restart_timeout=180
; OPcache error_log file name. Empty string assumes "stderr".
;opcache.error_log=
; All OPcache errors go to the Web server log.
; By default, only fatal errors (level 0) or errors (level 1) are logged.
; You can also enable warnings (level 2), info messages (level 3) or
; debug messages (level 4).
;opcache.log_verbosity_level=1
; Preferred Shared Memory back-end. Leave empty and let the system decide.
;opcache.preferred_memory_model=
; Protect the shared memory from unexpected writing during script execution.
; Useful for internal debugging only.
;opcache.protect_memory=0
; Allows calling OPcache API functions only from PHP scripts which path is
; started from specified string. The default "" means no restriction
;opcache.restrict_api=
; Enables and sets the second level cache directory.
; It should improve performance when SHM memory is full, at server restart or
; SHM reset. The default "" disables file based caching.
; RPM note : file cache directory must be owned by process owner
; for mod_php, see /etc/opt/remi/php82/httpd/conf.d/php.conf
; for php-fpm, see /etc/opt/remi/php82/php-fpm.d/*conf
;opcache.file_cache = /var/opt/remi/php82/lib/php/opcache
; Enables or disables opcode caching in shared memory.
opcache.file_cache_only=0
; Enables or disables checksum validation when script loaded from file cache.
opcache.file_cache_consistency_checks=1
; Implies opcache.file_cache_only=1 for a certain process that failed to
; reattach to the shared memory (for Windows only). Explicitly enabled file
; cache is required.
;opcache.file_cache_fallback=1
; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
; Under certain circumstances (if only a single global PHP process is
; started from which all others fork), this can increase performance
; by a tiny amount because TLB misses are reduced. On the other hand, this
; delays PHP startup, increases memory usage and degrades performance
; under memory pressure - use with care.
; Requires appropriate OS configuration.
opcache.huge_code_pages=0
; Validate cached file permissions.
; Leads OPcache to check file readability on each access to cached file.
; This directive should be enabled in shared hosting environment, when few
; users (PHP-FPM pools) reuse the common OPcache shared memory.
;opcache.validate_permission=0
; Prevent name collisions in chroot'ed environment.
; This directive prevents file name collisions in different "chroot"
; environments. It should be enabled for sites that may serve requests in
; different "chroot" environments.
;opcache.validate_root=0
; If specified, it produces opcode dumps for debugging different stages of
; optimizations.
;opcache.opt_debug_level=0
; Specifies a PHP script that is going to be compiled and executed at server
; start-up.
; https://php.net/opcache.preload
;opcache.preload=
; Preloading code as root is not allowed for security reasons. This directive
; facilitates to let the preloading to be run as another user.
; https://php.net/opcache.preload_user
;opcache.preload_user=
; Prevents caching files that are less than this number of seconds old. It
; protects from caching of incompletely updated files. In case all file updates
; on your site are atomic, you may increase performance by setting it to "0".
;opcache.file_update_protection=2
; Absolute path used to store shared lockfiles (for *nix only).
;opcache.lockfile_path=/tmp
Para ir ajustando la memoria y otros parámetros de Opcache, tienes que conocer tus estadísticas de uso. Puedes hacerlo con varios plugins en WordPress buscando Opcache en su directorio
O creando un archivo phpinfo ()
vi phpinfo.php
#Pones en el archivo lo siguiente y guardas con ESC wq!
<?php
phpinfo();
?>
Y lo guardas en el directorio raíz donde tengas tu web en el servidor. Y luego en el navegador pones tuweb.com/phpinfo.php. Hay un apartado para Opcache como este:
O simplemente en línea de comandos con:
php -r 'var_dump(opcache_get_status());'
- hits: Número de aciertos en el caché.
- misses: Número de fallos en el caché.
- cache_full: Si el caché está lleno o no.
- memory_usage: Información sobre el uso de memoria de OPCache.
Te he puesto mis valores, pero lo que tienes que hacer es ir probando poco a poco. Empieza por ejemplo con 128 MB de memoria, pasa a 256 MB y sigue subiendo si es necesario. Te vas fijando en el Hit Ratio de Opcache y tienes que conseguir que supere como mínimo el 90 %. Los otros parámetros son menos importantes.
Referencias:
- https://docs.rockylinux.org/guides/web/php/
- https://www.isscloud.io/guides/php-fpm-settings-for-best-performance/
- https://geekflare.com/php-fpm-optimization/
- https://tideways.com/profiler/blog/fine-tune-your-opcache-configuration-to-avoid-caching-suprises
- https://erikpoehler.com/2021/01/08/enable-configure-and-monitor-phps-opcache/
- https://rpms.remirepo.net/wizard/
- https://www.scalingphpbook.com/blog/2014/02/14/best-zend-opcache-settings.html
- https://www.virtualmin.com/docs/server-components/configuring-multiple-php-versions/