Aller au contenu

HTENGIX

HTENGIX is a patched NGINX build that does the one thing stock NGINX famously refuses to do: it reads .htaccess files from your docroot at request time, with Apache per-directory semantics.

Point it at an existing Apache document root and your mod_rewrite rules — WordPress pretty permalinks, security-plugin rules, custom redirects — just work, without translating them to nginx.conf syntax first.

Beta — testing channel

HTENGIX is in beta and ships exclusively through the getpagespeed-extras-testing channel. The supported directive scope is deliberately narrow (see what works today and what does not yet) and is expanding release by release. Don't run it on production workloads that depend on directives outside the supported scope.

What it does

With htaccess on; in your NGINX configuration, HTENGIX walks the filesystem path of each request and applies .htaccess files the way Apache does:

  • Per-directory rewrite semantics — patterns match the directory-relative path (no leading slash), relative substitutions re-anchor at the directory or RewriteBase, exactly like Apache's per-dir mod_rewrite.
  • Closest-wins merge order — the nearest .htaccess with rewrite configuration replaces ancestors' rewrite configuration, matching Apache's default (non-Inherit) behavior.
  • <IfModule> blocks — including negation and nesting; mod_rewrite counts as loaded, and blocks for unemulated modules are skipped wholesale, like Apache.
  • Nonexistent paths — requests to not-yet-existing paths fall back to the deepest existing ancestor directory, so front-controller patterns (WordPress, Laravel, etc.) rewrite correctly.
  • .ht* request denial — direct requests to .htaccess/.htpasswd return 403, matching Apache's stock FilesMatch "^\.ht" protection.
  • Change pickup without reloads.htaccess edits take effect on the next request (mtime-based cache), no nginx -s reload needed.

What works today

The mod_rewrite directive category is implemented in full and parity-tested 1:1 against Apache 2.4:

Area Coverage
RewriteRule Full flag set: L, NC, QSA, QSD, QSL, R[=code], F, G, END, PT, P, C, N, B, NE, NS, DPI, E=, CO=, T=, H=, -, S=n
RewriteCond All test operators: -f, -d, -e, -s, -l, -x, -U, -F, string and integer comparisons, ! negation, [OR]/[NC] flags, %{VARIABLE} server variables
RewriteBase, RewriteEngine, RewriteOptions Supported (RewriteOptions Inherit from .htaccess is a tracked gap)
<IfModule> containers Full, including nesting and ! negation

What does not work yet

Honesty over hype: HTENGIX currently warns and skips these .htaccess directive categories — they are parsed but not applied:

  • ErrorDocument
  • Options (including Options -Indexes)
  • Header (mod_headers)
  • ExpiresActive / ExpiresByType / ExpiresDefault (mod_expires)
  • Order / Allow / Deny and Require (access control)
  • Containers other than <IfModule><Files>, <FilesMatch>, <Limit> blocks are skipped whole (their contents are never mis-applied at top level)
  • RewriteOptions Inherit declared inside .htaccess

Each skipped directive is logged, so you can audit exactly what a given .htaccess needs beyond the supported scope. If your site's .htaccess relies on these, keep their NGINX equivalents (error_page, add_header, expires, deny) in your server block — the Apache to NGINX converter generates them.

There are also two documented engine divergences from Apache 2.4, asserted by regression tests so they never change silently:

URL shape Apache 2.4 HTENGIX
/a%2Fb (encoded slash) 404 (AllowEncodedSlashes off default) decodes; catch-all rules apply
/index.php/extra (PATH_INFO on a PHP-less baseline) 404 catch-all rules apply

How we test parity

"Parity-tested" is a concrete, reproducible claim, not a slogan:

  1. A differential harness boots real Apache httpd 2.4 and HTENGIX side by side over the same document root and the same unmodified .htaccess files.
  2. Both servers replay an identical set of request tuples (method, URI, headers), and the harness diffs the full outcome: status code, redirect Location, and the internally rewritten target.
  3. The fixtures are real-world: the canonical WordPress .htaccess (26 request tuples) and an anonymized real customer site running WordPress with Sucuri WAF rules (15 tuples). All 41/41 tuples match Apache 2.4.
  4. Every parity run is codified as a self-contained golden test in the module's CI, so any regression fails the build.
  5. The result was re-verified end-to-end from the published RPM on a clean Rocky Linux 9 container — not just from a development build.

How to install HTENGIX

HTENGIX is available for RHEL 9 / Rocky Linux 9 / AlmaLinux 9 (x86_64) from the getpagespeed-extras-testing channel, which requires an active repository subscription. More distributions will follow as the beta progresses.

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install dnf-plugins-core
dnf config-manager --set-enabled getpagespeed-extras-testing
dnf -y install htengix
systemctl enable --now nginx

HTENGIX provides nginx, so it slots in as a drop-in package replacement while preserving your existing configuration and installed modules:

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install dnf-plugins-core
dnf config-manager --set-enabled getpagespeed-extras-testing
dnf swap nginx htengix
service nginx upgrade

Then enable .htaccess processing for a server (or location) that serves an Apache-style docroot:

server {
    listen 80;
    server_name example.com;
    root /var/www/example.com;

    htaccess on;

    index index.php index.html;
    # ... your usual PHP-FPM / static config ...
}

Watch the error log on first requests: any .htaccess directive outside the supported scope is reported there, giving you a precise migration checklist.

How to switch back to stable NGINX

dnf config-manager --set-disabled getpagespeed-extras-testing
dnf swap htengix nginx

Your NGINX configuration is untouched by the swap; only the htaccess directive needs removing.

Feedback

HTENGIX is built in the open against real-world .htaccess corpora. If a rule from your site doesn't behave like it does under Apache, that's exactly the report we want — contact us with the .htaccess snippet and the request URL.