fix(apache): harden the default configuration - #49
Merged
zebby76 merged 1 commit intoSep 3, 2026
Merged
Conversation
Six defaults, all measured on smalswebtech/base-php:8.5.9-apache with an index.php, a .env, a config.yml, a .git/config and a .well-known/probe.txt in the docroot. Directory listing was on and the PHP front controller was not a DirectoryIndex candidate, so GET / returned "Index of /" listing every one of those files as a link rather than running the application. Options loses Indexes and index.php joins the DirectoryIndex list, which is what the nginx variant already does. Dotfiles were served: .env answered 200 with its contents, and so did .git/config. The rule is a RedirectMatch rather than a Require, for two reasons. It matches any path component, so a file inside a dot directory is covered -- .git/config would otherwise slip through a FilesMatch, its own name being an ordinary one. And it answers 404, the same as the nginx variant now does, which neither confirms what exists nor leaves the two images disagreeing about the same request. /.well-known keeps its normal handling. A FilesMatch stays behind it as an authorisation-layer backstop and as the broader form of the .ht* rule it replaces. TRACE answered 200. It echoes the request back and has no use here. ServerSignature defaulted to On, printing the server version and port in the footer of every error page while ServerTokens was already Prod -- the two settings disagreed about the same disclosure. Every start logged AH00558, apache having no ServerName to work from; it then guesses one from the container address, which lands in self-referential redirects. There is now an APACHE_SERVER_NAME variable, default default.localhost, matching the nginx server_name. The FastCGI handler URL carried a trailing slash, so SCRIPT_FILENAME reached php-fpm as //app/var/www/html/index.php. PHP realpath()s it, but it is in every access log line and some frameworks compare that string. request before after GET / 200 listing 200 front controller GET /.env 200 contents 404 GET /.git/config 200 contents 404 GET /.well-known/probe.txt 200 200 GET /index.php 200 200 TRACE / 200 405 error page footer <address>Apache Server at ... Port ...</address> none AH00558 at startup 2 0 SCRIPT_FILENAME //app/var/... /app/var/... /status, /server-status and /real-time-status still answer, and the rendered configuration passes httpd -t. /server-info answers 404 before and after this change: mod_info is not among the modules the template loads, so its IfModule block has never been active. Left alone here, it belongs with the module list.
Contributor
Author
|
Rebased on The assertions for this change (docroot not browsable, dotfiles 404, TRACE) are held back deliberately: #48 introduces the shared test plumbing they need — the |
zebby76
force-pushed
the
fix/apache-harden-default-configuration
branch
from
September 3, 2026 09:22
fb07adf to
7e430aa
Compare
This was referenced Sep 3, 2026
zebby76
added a commit
that referenced
this pull request
Sep 3, 2026
Six defaults, all measured on smalswebtech/base-php:8.5.9-apache with an index.php, a .env, a config.yml, a .git/config and a .well-known/probe.txt in the docroot. Directory listing was on and the PHP front controller was not a DirectoryIndex candidate, so GET / returned "Index of /" listing every one of those files as a link rather than running the application. Options loses Indexes and index.php joins the DirectoryIndex list, which is what the nginx variant already does. Dotfiles were served: .env answered 200 with its contents, and so did .git/config. The rule is a RedirectMatch rather than a Require, for two reasons. It matches any path component, so a file inside a dot directory is covered -- .git/config would otherwise slip through a FilesMatch, its own name being an ordinary one. And it answers 404, the same as the nginx variant now does, which neither confirms what exists nor leaves the two images disagreeing about the same request. /.well-known keeps its normal handling. A FilesMatch stays behind it as an authorisation-layer backstop and as the broader form of the .ht* rule it replaces. TRACE answered 200. It echoes the request back and has no use here. ServerSignature defaulted to On, printing the server version and port in the footer of every error page while ServerTokens was already Prod -- the two settings disagreed about the same disclosure. Every start logged AH00558, apache having no ServerName to work from; it then guesses one from the container address, which lands in self-referential redirects. There is now an APACHE_SERVER_NAME variable, default default.localhost, matching the nginx server_name. The FastCGI handler URL carried a trailing slash, so SCRIPT_FILENAME reached php-fpm as //app/var/www/html/index.php. PHP realpath()s it, but it is in every access log line and some frameworks compare that string. request before after GET / 200 listing 200 front controller GET /.env 200 contents 404 GET /.git/config 200 contents 404 GET /.well-known/probe.txt 200 200 GET /index.php 200 200 TRACE / 200 405 error page footer <address>Apache Server at ... Port ...</address> none AH00558 at startup 2 0 SCRIPT_FILENAME //app/var/... /app/var/... /status, /server-status and /real-time-status still answer, and the rendered configuration passes httpd -t. /server-info answers 404 before and after this change: mod_info is not among the modules the template loads, so its IfModule block has never been active. Left alone here, it belongs with the module list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six defaults in the apache variant, all measured on
smalswebtech/base-php:8.5.9-apachewith anindex.php, a.env, aconfig.yml, a.git/configand a.well-known/probe.txtin thedocroot.
Note the image renders its own
httpd.conffromconfig/apache2/httpd.conf.tmpl; thedistribution file is not used, so everything below is ours.
GET /200— Index of /200— front controllerGET /.env200—APP_SECRET=very-secret404GET /.git/config200—[remote "origin"] url = git@internal:app.git404GET /.well-known/probe.txt200200GET /index.php200200TRACE /200405<address>Apache Server at … Port …</address>AH00558at startupSCRIPT_FILENAME//app/var/www/html/index.php/app/var/www/html/index.phpWhat changed and why
Directory listing was on, and the PHP front controller was not a
DirectoryIndexcandidate.GET /returned a browsable listing of the docroot rather than running the application.Optionsloses
Indexes, andindex.phpjoins the list — which is what the nginx variant already doeswith
index index.php.Dotfiles were served. The rule is a
RedirectMatch, not aRequire, for two reasons:.git/configslips through a
FilesMatch, its own name being an ordinary one;Require all deniedgives 403,which confirms the file exists and leaves the two images disagreeing about the same request.
/.well-knownkeeps its normal handling. AFilesMatchstays behind the redirect as anauthorisation-layer backstop, and as the broader form of the
.ht*rule it replaces.TRACEanswered 200. It echoes the request back and has no use here.ServerSignaturedefaulted toOn— the server version and port in the footer of every errorpage, while
ServerTokenswas alreadyProd. The two settings disagreed about the samedisclosure.
Every start logged
AH00558, apache having noServerNameto work from; it then guesses onefrom the container address, which lands in self-referential redirects. New
APACHE_SERVER_NAME,default
default.localhost, matching the nginxserver_name.The FastCGI handler URL carried a trailing slash, so
SCRIPT_FILENAMEreached php-fpm as//app/var/www/html/index.php. PHPrealpath()s it, but it is in every access log line and someframeworks compare that string.
Not broken by this
/status,/server-statusand/real-time-statusstill answer, and the rendered configurationpasses
httpd -t(Syntax OK)./server-infoanswers404before and after —mod_infois not among the modules thetemplate loads, so its
<IfModule>block has never been active. Left alone here; it belongs withthe module list, which is a separate change.