From af9af7bf28cca1dc87e51f200c482cbecdb02831 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Wed, 2 Sep 2026 16:50:34 +0200 Subject: [PATCH 1/5] fix(roles/apache_httpd): load the cgi and cgid modules when enabled Both files wrapped their LoadModule in an MPM guard, so that the MPM in use picked the matching CGI module by itself. The guard could never match: the role's httpd.conf includes only mods-enabled/*.conf, httpd expands that glob alphabetically, and cgi.conf and cgid.conf both sort before mpm_event.conf, mpm_prefork.conf and mpm_worker.conf. At the point they were parsed no MPM was loaded yet, so the block was skipped and the module never made it into the running server, with httpd -t reporting Syntax OK throughout. Verified on Rocky 9 by reproducing the role's include layout: cgid_module is absent as shipped and present once the guard is gone, or once the MPM file is renamed to sort first. Drop the guards rather than renaming the MPM entries, because filename is part of the public interface that inventories override, and because a silently skipped module is exactly the failure mode the role avoids elsewhere. Which module to pair with which MPM is now stated in the template comments; measured on Rocky 9 and Debian 13, every pairing serves CGI, so it is a sizing decision rather than a hard requirement. The minimal PHP-FPM example enabled cgi although that vHost serves PHP through proxy_fcgi and never runs a CGI script, so following it produced a working site with the module silently absent - which is a large part of why this went unnoticed. Drop it. The non-hardened example keeps cgid, since it deliberately mirrors the stock RHEL configuration, but now says why and what it costs. --- CHANGELOG.md | 1 + roles/apache_httpd/EXAMPLES.md | 5 ++--- .../etc/httpd/mods-available/cgi.conf.j2 | 14 +++++++------- .../etc/httpd/mods-available/cgid.conf.j2 | 17 +++++++---------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 688bd1110..f2394fc41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **role:apache_httpd**: The `cgi` and `cgid` modules are loaded when the inventory enables them; until now the entry was accepted but the module never ended up in the running server. * **role:php**: The PHP-FPM slowlog holds the backtrace of a slow request on RedHat, instead of staying empty while php-fpm logs `failed to ptrace(ATTACH) child : Operation not permitted (1)`. * **role:openvpn_server**: A new server certificate, a changed `server.conf` or a regenerated Diffie-Hellman file restarts OpenVPN. Until now the files were written to disk while the running service kept its old configuration, so a renewed certificate only took effect at the next reboot. The certificate revocation list and the client configs still apply without a restart, since OpenVPN re-reads them per connection. * **role:apache_httpd**: Set `apache_httpd__mod_ssl_ssl_use_stapling` to off by default because Let's Encrypt does not provide an OCSP URL-endpoint. diff --git a/roles/apache_httpd/EXAMPLES.md b/roles/apache_httpd/EXAMPLES.md index b0216a912..a5f7e2db6 100644 --- a/roles/apache_httpd/EXAMPLES.md +++ b/roles/apache_httpd/EXAMPLES.md @@ -32,9 +32,6 @@ A minimal working "app" vHost definition for a PHP-FPM application, located unde ```yaml apache_httpd__conf_server_admin: 'webmaster@example.com' apache_httpd__mods__host_var: - - filename: 'cgi' - enabled: true - state: 'present' - filename: 'proxy_fcgi' enabled: true state: 'present' @@ -131,6 +128,8 @@ apache_httpd__vhosts__host_var: This is an Apache configuration that is close to the RHEL default configuration, without any CIS remediations. +It enables `cgid` because the stock RHEL configuration loads it, not because the vHost below needs it: that vHost is a pure reverse proxy and never runs a CGI script. Loading the module starts the external CGI daemon and creates its `ScriptSock` at every start, so drop the entry unless you actually serve CGI. + ```yaml apache_httpd__conf__host_var: - filename: 'expires' diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/cgi.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/cgi.conf.j2 index eb53b891b..f912b5cc9 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/cgi.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/cgi.conf.j2 @@ -1,12 +1,12 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} -# This configuration file loads a CGI module appropriate to the MPM -# which has been activated. mod_cgid should be used -# with a threaded MPM; mod_cgi with the prefork MPM. - - LoadModule cgi_module {{ __apache_httpd__modules_path }}/mod_cgi.so - +# mod_cgi forks a CGI process out of the httpd worker. Pair it with mpm_prefork; on a threaded MPM +# the fork replicates every thread of the parent, which is what mod_cgid exists to avoid. It does +# work on mpm_event and mpm_worker, so this is a sizing decision, not a hard requirement. +# Enable either this module or cgid, never both: they both handle `cgi-script`, and the one loaded +# first wins while the other only costs resources. +LoadModule cgi_module {{ __apache_httpd__modules_path }}/mod_cgi.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/cgid.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/cgid.conf.j2 index becf4fd2c..22bc896cb 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/cgid.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/cgid.conf.j2 @@ -1,15 +1,12 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} -# This configuration file loads a CGI module appropriate to the MPM -# which has been activated. mod_cgid should be used -# with a threaded MPM; mod_cgi with the prefork MPM. - - LoadModule cgid_module {{ __apache_httpd__modules_path }}/mod_cgid.so - - - LoadModule cgid_module {{ __apache_httpd__modules_path }}/mod_cgid.so - +# mod_cgid runs CGI scripts through an external daemon instead of forking out of a worker, which is +# what a threaded MPM wants. Pair it with mpm_event (the role default) or mpm_worker. The daemon and +# its ScriptSock are created at startup whether or not a CGI is ever requested. +# Enable either this module or cgi, never both: they both handle `cgi-script`, and the one loaded +# first wins while the other only costs resources. +LoadModule cgid_module {{ __apache_httpd__modules_path }}/mod_cgid.so From 8b9e2e2d422015e785c7e358dbb496d50e25b682 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Wed, 2 Sep 2026 16:51:28 +0200 Subject: [PATCH 2/5] style(roles/apache_httpd): set the qos mod to enabled: false The entry read enabled: true, state: 'absent', which reads as a contradiction: state: 'absent' removes the mods-available file and the mods-enabled symlink on its own, so enabled was inert. Debian and Ubuntu already say false. No change on a managed host, hence no CHANGELOG entry. --- roles/apache_httpd/vars/RedHat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/apache_httpd/vars/RedHat.yml b/roles/apache_httpd/vars/RedHat.yml index 989676292..cc6de5299 100644 --- a/roles/apache_httpd/vars/RedHat.yml +++ b/roles/apache_httpd/vars/RedHat.yml @@ -214,7 +214,7 @@ apache_httpd__mods__role_var: template: 'proxy_wstunnel' - filename: 'qos' - enabled: true + enabled: false state: 'absent' template: 'qos' From 4cf056fde51d0bdcd023ef9b676adad43cd49c09 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Wed, 2 Sep 2026 18:11:38 +0200 Subject: [PATCH 3/5] fix(roles/apache_httpd): use a valid module name in the userdir guard never matched: find_module() (server/core.c) resolves either the module's __FILE__ (mod_userdir.c) or its LoadModule symbol (userdir_module), and "userdir" is neither. Measured on Rocky 9 against a control token. The block was therefore never applied, so the php_admin_flag engine Off it wraps has never disabled PHP in /home/*/public_html. Latent rather than exploitable: the php conf is not enabled by default and the role does not manage mod_userdir, so no host had both pieces in place. Kept as a guard rather than removed, unlike the rest of this branch, precisely because mod_userdir is outside the role's control. --- .../templates/etc/httpd/conf-available/php.conf.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/apache_httpd/templates/etc/httpd/conf-available/php.conf.j2 b/roles/apache_httpd/templates/etc/httpd/conf-available/php.conf.j2 index 8bf6e5f91..0aef764a7 100644 --- a/roles/apache_httpd/templates/etc/httpd/conf-available/php.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/conf-available/php.conf.j2 @@ -1,5 +1,5 @@ # {{ ansible_managed }} -# 2021111201 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} @@ -33,7 +33,7 @@ AddType text/html .php # To re-enable PHP in user directories comment the following lines # (from to .) Do NOT set it to On as it # prevents .htaccess files from disabling it. - + php_admin_flag engine Off From d4b3398f08b2937b0493572e7067be7d2b3e3330 Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Wed, 2 Sep 2026 18:12:49 +0200 Subject: [PATCH 4/5] fix(roles/apache_httpd)!: stop hiding module mismatches behind IfModule An around a module's own directives turns a configuration mistake into silence: httpd -t reports Syntax OK and the setting never applies, which is the failure mode this role avoids everywhere else. Drop the guards from the conf-available snippets and from the guarded LoadModule lines. Two of the defaults were inconsistent and only worked because of the guard: the deflate and mod_security snippets were enabled while their modules were not, so compression has silently been off and ModSecurity's PCRE tuning has never applied. Disable the snippets to match the modules, which keeps the effective behaviour of every host unchanged; whether compression should be on by default is a separate question. The two EXAMPLES that enabled the deflate module now enable the snippet alongside it, so those recipes still compress. Removing each guard was measured on Rocky 9 rather than assumed: - deflate, mod_security, autoindex: the directives now abort with AH00526 Syntax error when the module is absent, which is the point - proxy_fcgi/_http/_wstunnel: without mod_proxy the load fails with "Cannot load mod_proxy_fcgi.so", clearer than a skipped LoadModule - authn_file, authz_host, authz_user: mod_authz_host loads perfectly well without authz_core, so the guard protected nothing at all - php7, php8: on a threaded MPM PHP itself refuses with "your PHP Module is not compiled to be threadsafe" and Apache does not start, a better message than any guard produces autoindex has a template but was missing from apache_httpd__conf__role_var; add it as enabled: false, state: 'absent' so the role's coverage is visible. mods-available/wsgi_python3.conf keeps : a negated guard around its own LoadModule is the correct idiom for not loading two mod_wsgi builds into one process. BREAKING CHANGE: a conf-available snippet and the module it configures have to be enabled together; the deflate and mod_security snippets are no longer enabled by default, so a host that enabled only the deflate module loses compression. --- CHANGELOG.md | 1 + roles/apache_httpd/EXAMPLES.md | 14 ++++ roles/apache_httpd/README.md | 6 +- roles/apache_httpd/defaults/main.yml | 9 +- .../httpd/conf-available/autoindex.conf.j2 | 82 +++++++++---------- .../etc/httpd/conf-available/deflate.conf.j2 | 22 +++-- .../httpd/conf-available/mod_security.conf.j2 | 12 +-- .../httpd/mods-available/authn_file.conf.j2 | 8 +- .../httpd/mods-available/authz_host.conf.j2 | 8 +- .../httpd/mods-available/authz_user.conf.j2 | 8 +- .../etc/httpd/mods-available/php7.conf.j2 | 11 +-- .../etc/httpd/mods-available/php8.conf.j2 | 11 +-- .../httpd/mods-available/proxy_fcgi.conf.j2 | 10 +-- .../httpd/mods-available/proxy_http.conf.j2 | 8 +- .../mods-available/proxy_wstunnel.conf.j2 | 8 +- 15 files changed, 121 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2394fc41..dfd491978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes +* **role:apache_httpd**: A `conf-available` snippet and the Apache module it configures have to be enabled together from now on. The snippets no longer carry an `` guard, so enabling one without its module aborts the run at the config test instead of being ignored in silence. Getting the role's own defaults consistent changed two of them: the `deflate` and `mod_security` snippets are no longer enabled, matching their modules, which never were. A host that switched the `deflate` module on in its inventory had compression and loses it, so enable the snippet alongside the module to keep it. * **role:php**: On RedHat, every host running this role gets the `lfops_php_fpm_slowlog` SELinux policy module, which grants the `httpd_t` domain the `sys_ptrace` capability and `ptrace` on itself. Without it the PHP-FPM slowlog stays empty, because the master is not allowed to ptrace the worker whose backtrace it is supposed to write. The module is deployed regardless of whether the slowlog is switched on, the permissions apply to the whole `httpd_t` domain and therefore to Apache httpd as well, and compiling it installs `make` and `selinux-policy-devel` on the host. The `php` and `icingaweb2` playbooks run the `policycoreutils` and `selinux` roles for this; set the playbook's `__skip_selinux` variable (for example `php__skip_selinux: true`) to leave the host's policy untouched. * **role:mariadb_server**: The InnoDB buffer pool grows from 128 MiB to 512 MiB, so a database with more than a trivial amount of data is served from memory instead of from disk. Every host running this role therefore uses roughly 384 MiB more RAM after the next restart of the service. * **role:mariadb_server**: The InnoDB redo log grows from 32 MiB to the 96 MiB MariaDB itself ships, so a write-heavy server no longer stalls waiting for a checkpoint on a redo log sized for much smaller workloads. InnoDB resizes the log itself when the service next restarts, also after an unclean shutdown, but the data directory needs 64 MiB more free space for it; check that on hosts that are tight before deploying. Set `mariadb_server__cnf_innodb_log_file_size__group_var: '32M'` (or the `__host_var`) to keep the previous size. diff --git a/roles/apache_httpd/EXAMPLES.md b/roles/apache_httpd/EXAMPLES.md index a5f7e2db6..2e9bb5872 100644 --- a/roles/apache_httpd/EXAMPLES.md +++ b/roles/apache_httpd/EXAMPLES.md @@ -44,7 +44,17 @@ apache_httpd__vhosts__host_var: ### Reverse Proxy +Compression is off by default, so this example turns it on: `mod_deflate` and `mod_filter` supply +the filter, and the `deflate` conf snippet is what actually applies it to the MIME types listed in +it. Both halves are needed; enabling only the modules compresses nothing, enabling only the conf +fails the config test. + ```yaml +apache_httpd__conf__host_var: + - filename: 'deflate' + enabled: true + state: 'present' + template: 'deflate' apache_httpd__mods__host_var: - filename: 'deflate' enabled: true @@ -132,6 +142,10 @@ It enables `cgid` because the stock RHEL configuration loads it, not because the ```yaml apache_httpd__conf__host_var: + - filename: 'deflate' + enabled: true + state: 'present' + template: 'deflate' - filename: 'expires' enabled: false state: 'present' diff --git a/roles/apache_httpd/README.md b/roles/apache_httpd/README.md index 26f420942..b829e732c 100644 --- a/roles/apache_httpd/README.md +++ b/roles/apache_httpd/README.md @@ -23,7 +23,11 @@ The config is split into several files forming the configuration hierarchy outli `-- sites-enabled/ ``` -We avoid using `` in vHost definitions and in the global `httpd.conf` to facilitate debugging. Without ``, a missing module causes a clear startup error instead of silently dropping configuration. `` is only used in `mods-available/` and `conf-available/` where it is necessary to guard module-specific configuration. +We avoid `` throughout. Without it, a missing module causes a clear startup error instead of silently dropping configuration, which is otherwise impossible to spot: `httpd -t` reports `Syntax OK` and the setting simply never applies. + +The consequence is a pairing rule: **a `conf-available` snippet and the module it configures have to be enabled together.** Enabling the `deflate` snippet without `mod_deflate` and `mod_filter` aborts the run at the config test with `Invalid command 'AddOutputFilterByType'`, which is the intended behaviour. The role's own defaults are consistent, so this only concerns snippets and modules you enable yourself via `apache_httpd__conf__*_var` and `apache_httpd__mods__*_var`. + +Two exceptions remain, both guarding against something other than a mismatch: `mods-available/wsgi_python3.conf` uses `` so that two mod_wsgi builds never load into one process, and `conf-available/php.conf` guards its user-directory block with `` because the role does not manage mod_userdir at all. `mod_info` is not enabled. It serves the complete configuration on `/server-info`, including the credentials other modules carry in their directives. The endpoint stays configured in the localhost vHost and answers with an empty response until the `info` module is enabled via `apache_httpd__mods__group_var` / `apache_httpd__mods__host_var`. diff --git a/roles/apache_httpd/defaults/main.yml b/roles/apache_httpd/defaults/main.yml index fe27802ac..838df9a04 100644 --- a/roles/apache_httpd/defaults/main.yml +++ b/roles/apache_httpd/defaults/main.yml @@ -55,8 +55,13 @@ apache_httpd__conf__group_var: [] apache_httpd__conf__host_var: [] # Apache conf used internally by this role. apache_httpd__conf__role_var: + - filename: 'autoindex' + enabled: false + state: 'absent' + template: 'autoindex' + - filename: 'deflate' - enabled: true + enabled: false state: 'present' template: 'deflate' @@ -113,7 +118,7 @@ apache_httpd__conf__role_var: template: 'mime_magic' - filename: 'mod_security' - enabled: true + enabled: false state: 'present' template: 'mod_security' diff --git a/roles/apache_httpd/templates/etc/httpd/conf-available/autoindex.conf.j2 b/roles/apache_httpd/templates/etc/httpd/conf-available/autoindex.conf.j2 index 4e1f78414..11bf0c083 100644 --- a/roles/apache_httpd/templates/etc/httpd/conf-available/autoindex.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/conf-available/autoindex.conf.j2 @@ -1,47 +1,47 @@ # {{ ansible_managed }} -# 2022102501 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} -# mod_autoindex.c - - Alias /icons/ "/usr/share/httpd/icons/" - - Options Indexes MultiViews FollowSymlinks - AllowOverride None - Require all granted - - AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip - AddIconByType (IMG,/icons/image2.gif) image/* - AddIconByType (SND,/icons/sound2.gif) audio/* - AddIconByType (TXT,/icons/text.gif) text/* - AddIconByType (VID,/icons/movie.gif) video/* - AddIcon /icons/a.gif .ps .ai .eps - AddIcon /icons/back.gif .. - AddIcon /icons/binary.gif .bin .exe - AddIcon /icons/binhex.gif .hqx - AddIcon /icons/blank.gif ^^BLANKICON^^ - AddIcon /icons/bomb.gif */core.* - AddIcon /icons/bomb.gif /core - AddIcon /icons/c.gif .c - AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip - AddIcon /icons/dvi.gif .dvi - AddIcon /icons/f.gif .for - AddIcon /icons/folder.gif ^^DIRECTORY^^ - AddIcon /icons/hand.right.gif README - AddIcon /icons/layout.gif .html .shtml .htm .pdf - AddIcon /icons/p.gif .pl .py - AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl - AddIcon /icons/tar.gif .tar - AddIcon /icons/tex.gif .tex - AddIcon /icons/text.gif .txt - AddIcon /icons/uuencoded.gif .uu - AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv - DefaultIcon /icons/unknown.gif +# autoindex_module +# Requires the autoindex module. No guard: enabling this conf without the module has to +# fail the config test rather than do nothing in silence. +Alias /icons/ "/usr/share/httpd/icons/" + + Options Indexes MultiViews FollowSymlinks + AllowOverride None + Require all granted + +AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip +AddIconByType (IMG,/icons/image2.gif) image/* +AddIconByType (SND,/icons/sound2.gif) audio/* +AddIconByType (TXT,/icons/text.gif) text/* +AddIconByType (VID,/icons/movie.gif) video/* +AddIcon /icons/a.gif .ps .ai .eps +AddIcon /icons/back.gif .. +AddIcon /icons/binary.gif .bin .exe +AddIcon /icons/binhex.gif .hqx +AddIcon /icons/blank.gif ^^BLANKICON^^ +AddIcon /icons/bomb.gif */core.* +AddIcon /icons/bomb.gif /core +AddIcon /icons/c.gif .c +AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip +AddIcon /icons/dvi.gif .dvi +AddIcon /icons/f.gif .for +AddIcon /icons/folder.gif ^^DIRECTORY^^ +AddIcon /icons/hand.right.gif README +AddIcon /icons/layout.gif .html .shtml .htm .pdf +AddIcon /icons/p.gif .pl .py +AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl +AddIcon /icons/tar.gif .tar +AddIcon /icons/tex.gif .tex +AddIcon /icons/text.gif .txt +AddIcon /icons/uuencoded.gif .uu +AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv +DefaultIcon /icons/unknown.gif - HeaderName /HEADER.html - ReadmeName README.html - IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t assets favicon* - IndexStyleSheet "/assets/style.css" - +HeaderName /HEADER.html +ReadmeName README.html +IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t assets favicon* +IndexStyleSheet "/assets/style.css" diff --git a/roles/apache_httpd/templates/etc/httpd/conf-available/deflate.conf.j2 b/roles/apache_httpd/templates/etc/httpd/conf-available/deflate.conf.j2 index e449a33c4..de819252f 100644 --- a/roles/apache_httpd/templates/etc/httpd/conf-available/deflate.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/conf-available/deflate.conf.j2 @@ -1,17 +1,15 @@ # {{ ansible_managed }} -# 2022082301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} -# mod_deflate.c - - # mod_filter.c - - AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript - AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript - AddOutputFilterByType DEFLATE application/rss+xml - AddOutputFilterByType DEFLATE application/wasm - AddOutputFilterByType DEFLATE application/xml - - +# deflate_module, filter_module +# AddOutputFilterByType comes from mod_filter and the DEFLATE filter from mod_deflate, so this +# snippet needs both modules enabled. It carries no guard on purpose: enabling the conf +# without the modules has to fail the config test rather than compress nothing in silence. +AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript +AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript +AddOutputFilterByType DEFLATE application/rss+xml +AddOutputFilterByType DEFLATE application/wasm +AddOutputFilterByType DEFLATE application/xml diff --git a/roles/apache_httpd/templates/etc/httpd/conf-available/mod_security.conf.j2 b/roles/apache_httpd/templates/etc/httpd/conf-available/mod_security.conf.j2 index a2d27d2ff..6092038a2 100644 --- a/roles/apache_httpd/templates/etc/httpd/conf-available/mod_security.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/conf-available/mod_security.conf.j2 @@ -1,12 +1,12 @@ # {{ ansible_managed }} -# 2021120101 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} # security2_module - - SecDataDir /tmp/ - SecPcreMatchLimit 100000 - SecPcreMatchLimitRecursion 100000 - +# Requires the security2 module. No guard: enabling this conf without the module has to +# fail the config test rather than leave the tuning silently unapplied. +SecDataDir /tmp/ +SecPcreMatchLimit 100000 +SecPcreMatchLimitRecursion 100000 diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/authn_file.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/authn_file.conf.j2 index 59331546d..65405139f 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/authn_file.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/authn_file.conf.j2 @@ -1,9 +1,9 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - LoadModule authn_file_module {{ __apache_httpd__modules_path }}/mod_authn_file.so - +# The Require directives this module provides are consumed by mod_authn_core, which the role enables +# by default. Loading it without authn_core is not an error, so there is nothing to guard against. +LoadModule authn_file_module {{ __apache_httpd__modules_path }}/mod_authn_file.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/authz_host.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/authz_host.conf.j2 index fcda031d4..33f5579ec 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/authz_host.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/authz_host.conf.j2 @@ -1,9 +1,9 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - LoadModule authz_host_module {{ __apache_httpd__modules_path }}/mod_authz_host.so - +# The Require directives this module provides are consumed by mod_authz_core, which the role enables +# by default. Loading it without authz_core is not an error, so there is nothing to guard against. +LoadModule authz_host_module {{ __apache_httpd__modules_path }}/mod_authz_host.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/authz_user.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/authz_user.conf.j2 index e5cbbcf04..742cfd56d 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/authz_user.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/authz_user.conf.j2 @@ -1,9 +1,9 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - LoadModule authz_user_module {{ __apache_httpd__modules_path }}/mod_authz_user.so - +# The Require directives this module provides are consumed by mod_authz_core, which the role enables +# by default. Loading it without authz_core is not an error, so there is nothing to guard against. +LoadModule authz_user_module {{ __apache_httpd__modules_path }}/mod_authz_user.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/php7.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/php7.conf.j2 index 238387c30..1725afbe8 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/php7.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/php7.conf.j2 @@ -1,11 +1,12 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} # PHP 7 - - LoadModule php7_module {{ __apache_httpd__modules_path }}/libphp7.so - -# otherwise use PHP-FPM +# mod_php needs a non-threaded MPM (mpm_prefork). No guard: on a threaded MPM PHP itself +# refuses with "Apache is running a threaded MPM, but your PHP Module is not compiled to be +# threadsafe" and Apache does not start, which is a better message than a skipped LoadModule. +# The role defaults to mpm_event, so use PHP-FPM there instead. +LoadModule php7_module {{ __apache_httpd__modules_path }}/libphp7.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/php8.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/php8.conf.j2 index cecc8a033..e74ae6842 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/php8.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/php8.conf.j2 @@ -1,11 +1,12 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} # PHP 8+ - - LoadModule php_module {{ __apache_httpd__modules_path }}/libphp.so - -# otherwise use PHP-FPM +# mod_php needs a non-threaded MPM (mpm_prefork). No guard: on a threaded MPM PHP itself +# refuses with "Apache is running a threaded MPM, but your PHP Module is not compiled to be +# threadsafe" and Apache does not start, which is a better message than a skipped LoadModule. +# The role defaults to mpm_event, so use PHP-FPM there instead. +LoadModule php_module {{ __apache_httpd__modules_path }}/libphp.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_fcgi.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_fcgi.conf.j2 index ca984375d..961b990c3 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_fcgi.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_fcgi.conf.j2 @@ -1,10 +1,10 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - # Needed for PHP-FPM, for example. - LoadModule proxy_fcgi_module {{ __apache_httpd__modules_path }}/mod_proxy_fcgi.so - +# Needed for PHP-FPM, for example. +# Requires mod_proxy, which the role enables by default. Without it the load fails with +# "Cannot load mod_proxy_fcgi.so", which says more than a silently skipped LoadModule. +LoadModule proxy_fcgi_module {{ __apache_httpd__modules_path }}/mod_proxy_fcgi.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_http.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_http.conf.j2 index 68a1695b4..3dec979a8 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_http.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_http.conf.j2 @@ -1,9 +1,9 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - LoadModule proxy_http_module {{ __apache_httpd__modules_path }}/mod_proxy_http.so - +# Requires mod_proxy, which the role enables by default. Without it the load fails with +# "Cannot load mod_proxy_http.so", which says more than a silently skipped LoadModule. +LoadModule proxy_http_module {{ __apache_httpd__modules_path }}/mod_proxy_http.so diff --git a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_wstunnel.conf.j2 b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_wstunnel.conf.j2 index f5172f515..e934665b3 100644 --- a/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_wstunnel.conf.j2 +++ b/roles/apache_httpd/templates/etc/httpd/mods-available/proxy_wstunnel.conf.j2 @@ -1,9 +1,9 @@ # {{ ansible_managed }} -# 2021110301 +# 2026090201 {% if item['by_role'] | d() %} # Generated by Ansible role: {{ item['by_role'] }} {% endif %} - - LoadModule proxy_wstunnel_module {{ __apache_httpd__modules_path }}/mod_proxy_wstunnel.so - +# Requires mod_proxy, which the role enables by default. Without it the load fails with +# "Cannot load mod_proxy_wstunnel.so", which says more than a silently skipped LoadModule. +LoadModule proxy_wstunnel_module {{ __apache_httpd__modules_path }}/mod_proxy_wstunnel.so From 1eb085c6b46442aa859597f97dc8afcde0881aae Mon Sep 17 00:00:00 2001 From: Navid Sassan Date: Wed, 2 Sep 2026 18:22:36 +0200 Subject: [PATCH 5/5] docs(roles/apache_httpd): correct the documented SSLUseStapling default --- roles/apache_httpd/README.md | 6 +++--- roles/apache_httpd/meta/argument_specs.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/apache_httpd/README.md b/roles/apache_httpd/README.md index b829e732c..7a2dfef5d 100644 --- a/roles/apache_httpd/README.md +++ b/roles/apache_httpd/README.md @@ -879,14 +879,14 @@ apache_httpd__skip_mod_security_coreruleset: true `apache_httpd__mod_ssl_ssl_use_stapling` -* See [SSLUseStapling](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslusestapling). +* Whether the server staples an OCSP response into the TLS handshake. Off, because a Let's Encrypt certificate carries no OCSP responder URL, so there is nothing to staple and Apache logs a warning per certificate on every start. Set it to `'on'` for a certificate from a CA that does publish an OCSP endpoint. See [SSLUseStapling](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslusestapling). * Type: String. -* Default: `'on'` +* Default: `'off'` Example: ```yaml # optional - mod_ssl -apache_httpd__mod_ssl_ssl_use_stapling: 'on' +apache_httpd__mod_ssl_ssl_use_stapling: 'off' ``` diff --git a/roles/apache_httpd/meta/argument_specs.yml b/roles/apache_httpd/meta/argument_specs.yml index 43de84ada..cec8cf649 100644 --- a/roles/apache_httpd/meta/argument_specs.yml +++ b/roles/apache_httpd/meta/argument_specs.yml @@ -277,7 +277,7 @@ argument_specs: apache_httpd__mod_ssl_ssl_use_stapling: type: 'str' required: false - default: 'on' + default: 'off' description: >- Whether OCSP stapling is used (`SSLUseStapling`).