.htaccess for nginx enables the nginx high performance webserver to deal with .htaccess
files.
.htaccess
files are mainly used for access control and URL rewrite instructions and are widely known across the web community. Originally designed for Apache, there is no native implementation available for nginx. While there is a legitimate reason for this, there would be huge practical benefit if nginx was able to support this.
.htaccess for nginx is efficient and elegant, using micro caching and various performance tweaks right out of the box. It is effortless in its installation and usage. The plugin's deeply integrated approach is ideal for webhosters, who are looking for mixed technology solutions using only nginx and nothing else.
- Apache is slow.
- Apache is wasting resources.
- Compared to nginx, Apache is poorly and inconsistently designed.
- Apache's monolithic design prevents it from scaling properly, while nginx is capable of handling tens of thousands of simultaneous connections with ease.
- Switching to nginx heavily improves performance, efficiency and security.
When using nginx, there are many legitimate reasons to support .htaccess
files.
- Mixed technology. Imagine using NodeJS and PHP side by side, running on one stable nginx webserver. When dealing with customer webspace, using Apache and nginx together (one proxying the other) is possible, however this adds unnecessary layers of redundancy and heavily wastes valuable server resources.
- Ease of use. Everybody knows how to use
.htaccess
files. As of January 2020, more than 24% of all active websites are still run on Apache and thus capable of utilizing.htaccess
files. If nginx had a way to support this feature, this number would be going down significantly, making the web faster. - Legacy. Just use your old code, without worrying if someone could access a protected directory inside any library you just forgot to handle in your nginx config.
- Plug and play. No need to convert
.htaccess
files for nginx and fix all the errors, rant about unsupported or oddly mixed up auto-generated config goo coming from a random online converter. - Justified. Apache performs multiple file reads anyway, so .htaccess for nginx cannot make it worse than Apache, right? In fact, with our built-in micro caching mechanism both, CPU and I/O load are reduced drastically compared to Apache's implementation.
- For webhosters. Today, webhosters still need to provide an interface for their customers to change certain aspects of their webserver's behaviour. The decades long and proven
.htaccess
file does just that.
.htaccess for nginx is incredibly lightweight and fast! It is written from the ground up with performance optimizations in mind. Even with low-end hardware it adds less than 1 millisecond to your response time, despite supporting quite complex rewrite structures with server variables.
Physical memory usage of this plugin is insanely low, under 10 KB for each nginx worker process, and it doesn't increase with more requests.
- Debian or Fedora environment
nginx
v1.19+ with Lua modulecurl
command-line tool- Optional:
htpasswd
utility (apache2-utils
package) for.htpasswd
hashing functions (required for Basic HTTP Authentication) - Optional:
getent
utility (libc-bin
package) for hostname lookups (e.g.Deny from _domainname.tld_
)
-
Install nginx with the Lua module
libnginx-mod-http-lua
and theluajit
package.- Debian:
apt-get install nginx libnginx-mod-http-lua luajit
- Fedora:
yum install nginx libnginx-mod-http-lua luajit
- Debian:
-
Verify that the Lua module is properly installed by running:
nginx -V 2>&1 | tr ' ' '\n' | grep lua
It should display something similar to this:
--add-module=/lua-nginx-module-a318d250f547c854ea2b091d0e06372ac0c00abc --add-module=/lua-upstream-nginx-module-0.07 --add-module=/stream-lua-nginx-module-9ce0848cff7c3c5eb0a7d5adfe2de22ea98e1abc
-
Build and install the plugin into an appropriate directory accessible by the nginx process, e.g.,
luajit -b htaccess.lua /etc/nginx/lua/htaccess.lbc
-
Add the following configuration to the nginx
http {}
context:http { ... lua_shared_dict htaccess 16m; ... }
This represents a caching system, used on a short-term per-request basis.
.htaccess
lines are usually cached as values for less than 100 milliseconds, but kept in memory as long as there are active connections. You can choose to assign any other memory amount to it, although 16 MB should be more than enough. -
Configure the nginx
server {}
context(s) to use the plugin:server { ... rewrite_by_lua_file /path/to/htaccess.lua; # or reference the bytecode instead # rewrite_by_lua_file /path/to/htaccess.lbc; ... }
Create an .htaccess
file in a directory of your host with the following content:
Order deny,allow
Deny from all
When trying to access a file inside this directory through your browser, access should be denied by receiving an HTTP 403
response.
The following tables came from this page.
Module | Section | Supported | Notes |
---|---|---|---|
core | <Else> |
No | |
core | <ElseIf> |
No | |
core | <Files> |
Yes | |
core | <FilesMatch> |
Yes | |
core | <If> |
No | |
core | <IfDefine> |
Never | Impossible to be implemented. Apache specific |
core | <IfDirective> |
Yes | |
core | <IfFile> |
Yes | |
core | <IfModule> |
Yes | Emulating supported modules according to supported directives |
core | <IfSection> |
Yes | |
core | <Limit> |
Yes | |
core | <LimitExcept> |
Yes | |
mod_authz_core | <RequireAll> |
No | |
mod_authz_core | <RequireAny> |
No | |
mod_authz_core | <RequireNone> |
No | |
mod_version | <IfVersion> |
Yes | The version will be simulated as Apache 2.4.0 |
Directives not listed below are not supported.
Module | Directive | Supported | Notes |
---|---|---|---|
core | AcceptPathInfo |
No | |
core | AddDefaultCharset |
No | |
core | CGIMapExtension |
No | |
core | CGIPassAuth |
No | |
core | CGIVar |
No | |
core | ContentDigest |
No | |
core | DefaultType |
No | |
core | EnableMMAP |
No | |
core | EnableSendfile |
No | |
core | ErrorDocument |
No | |
core | FileETag |
No | |
core | ForceType |
No | |
core | LimitRequestBody |
No | |
core | LimitXMLRequestBody |
No | |
core | Options |
No | |
core | QualifyRedirectURL |
No | |
core | RLimitCPU |
Never | Rarely used and not practical for nginx |
core | RLimitMEM |
Never | Rarely used and not practical for nginx |
core | RLimitNPROC |
Never | Rarely used and not practical for nginx |
core | ScriptInterpreterSource |
No | |
core | ServerSignature |
No | |
core | SetHandler |
No | |
core | SetInputFilter |
No | |
core | SetOutputFilter |
No | |
mod_access_compat | Allow |
Yes | Allow from domainname.tld requires getent command line tool |
mod_access_compat | Deny |
Yes | Deny from domainname.tld requires getent command line tool |
mod_access_compat | Order |
Yes | |
mod_access_compat | Satisfy |
Never | Security reasons. Satisfy All assumed |
mod_actions | Action |
Never | Security reasons. CGI request handling must be in main host config |
mod_alias | Redirect |
Yes | |
mod_alias | RedirectMatch |
Yes | |
mod_alias | RedirectPermanent |
Yes | |
mod_alias | RedirectTemp |
Yes | |
mod_auth_basic | AuthBasicAuthoritative |
No | |
mod_auth_basic | AuthBasicFake |
No | |
mod_auth_basic | AuthBasicProvider |
No | |
mod_auth_basic | AuthBasicUseDigestAlgorithm |
No | |
mod_auth_digest | * |
No | |
mod_auth_form | * |
No | |
mod_authn_anon | * |
No | |
mod_authn_core | AuthName |
Yes | |
mod_authn_core | AuthType |
Partially | Only AuthType Basic supported |
mod_authn_dbm | * |
No | |
mod_authn_file | AuthUserFile |
Yes | |
mod_authn_socache | * |
No | |
mod_authnz_ldap | * |
No | |
mod_authz_core | AuthMerging |
No | |
mod_authz_core | Require |
Partially | Require group, host, expr not supported |
mod_authz_dbm | * |
No | |
mod_authz_groupfile | * |
No | |
mod_autoindex | AddAlt |
No | |
mod_autoindex | AddAltByEncoding |
No | |
mod_autoindex | AddAltByType |
No | |
mod_autoindex | AddDescription |
No | |
mod_autoindex | AddIcon |
No | |
mod_autoindex | AddIconByEncoding |
No | |
mod_autoindex | AddIconByType |
No | |
mod_autoindex | DefaultIcon |
No | |
mod_autoindex | HeaderName |
No | |
mod_autoindex | IndexHeadInsert |
No | |
mod_autoindex | IndexIgnore |
No | |
mod_autoindex | IndexIgnoreReset |
No | |
mod_autoindex | IndexOptions |
No | |
mod_autoindex | IndexOrderDefault |
No | |
mod_autoindex | IndexStyleSheet |
No | |
mod_autoindex | ReadmeName |
No | |
mod_cern_meta | * |
No | Rarely used |
mod_charset_lite | CharsetDefault |
No | |
mod_charset_lite | CharsetOptions |
No | |
mod_charset_lite | CharsetSourceEnc |
No | |
mod_dir | DirectoryCheckHandler |
No | |
mod_dir | DirectoryIndex |
No | |
mod_dir | DirectoryIndexRedirect |
No | |
mod_dir | DirectorySlash |
No | |
mod_dir | FallbackResource |
No | |
mod_env | PassEnv |
No | |
mod_env | SetEnv |
No | |
mod_env | UnsetEnv |
No | |
mod_expires | ExpiresActive |
No | |
mod_expires | ExpiresByType |
No | |
mod_expires | ExpiresDefault |
No | |
mod_filter | AddOutputFilterByType |
No | |
mod_filter | FilterChain |
No | |
mod_filter | FilterDeclare |
No | |
mod_filter | FilterProtocol |
No | |
mod_filter | FilterProvider |
No | |
mod_headers | Header |
No | |
mod_headers | RequestHeader |
No | |
mod_imagemap | * |
No | |
mod_include | SSIErrorMsg |
No | |
mod_include | SSITimeFormat |
No | |
mod_include | SSIUndefinedEcho |
No | |
mod_include | XBitHack |
No | |
mod_isapi | * |
No | |
mod_ldap | * |
No | |
mod_logio | * |
No | |
mod_lua | * |
No | |
mod_mime | AddCharset |
No | |
mod_mime | AddEncoding |
No | |
mod_mime | AddHandler |
No | |
mod_mime | AddInputFilter |
No | |
mod_mime | AddLanguage |
No | |
mod_mime | AddOutputFilter |
No | |
mod_mime | AddType |
Yes | |
mod_mime | DefaultLanguage |
No | |
mod_mime | MultiviewsMatch |
No | |
mod_mime | RemoveCharset |
No | |
mod_mime | RemoveEncoding |
No | |
mod_mime | RemoveHandler |
No | |
mod_mime | RemoveInputFilter |
No | |
mod_mime | RemoveLanguage |
No | |
mod_mime | RemoveOutputFilter |
No | |
mod_mime | RemoveType |
No | |
mod_negotiation | ForceLanguagePriority |
No | |
mod_negotiation | LanguagePriority |
No | |
mod_reflector | * |
Never | Security reasons |
mod_rewrite | RewriteBase |
Yes | |
mod_rewrite | RewriteCond |
Partial | Environment (E=) flag is unsupported, as are CondPattern integer comparisons and some file attribute tests listed in the documentation |
mod_rewrite | RewriteEngine |
Yes | |
mod_rewrite | RewriteOptions |
No | |
mod_rewrite | RewriteRule |
Yes | |
mod_session | * |
No | |
mod_setenvif | BrowserMatch |
No | |
mod_setenvif | BrowserMatchNoCase |
No | |
mod_setenvif | SetEnvIf |
No | |
mod_setenvif | SetEnvIfExpr |
No | |
mod_setenvif | SetEnvIfNoCase |
No | |
mod_speling | CheckCaseOnly |
No | |
mod_speling | CheckSpelling |
No | |
mod_ssl | SSLCipherSuite |
No | |
mod_ssl | SSLOptions |
No | |
mod_ssl | SSLRenegBufferSize |
No | |
mod_ssl | SSLRequire |
No | |
mod_ssl | SSLRequireSSL |
No | |
mod_ssl | SSLUserName |
No | |
mod_ssl | SSLVerifyClient |
No | |
mod_ssl | SSLVerifyDepth |
No | |
mod_substitute | Substitute |
No | |
mod_substitute | SubstituteInheritBefore |
No | |
mod_substitute | SubstituteMaxLineLength |
No | |
mod_usertrack | CookieDomain |
No | |
mod_usertrack | CookieExpires |
No | |
mod_usertrack | CookieHTTPOnly |
No | |
mod_usertrack | CookieName |
No | |
mod_usertrack | CookieSameSite |
No | |
mod_usertrack | CookieSecure |
No | |
mod_usertrack | CookieStyle |
No | |
mod_usertrack | CookieTracking |
No |
Variables not listed below are not supported.
Variable | Supported | Notes |
---|---|---|
HTTP_* |
Yes | all standard and non-standard HTTP header fields are supported |
HTTPS |
Yes | |
DOCUMENT_ROOT |
Yes | |
SERVER_ADDR |
Yes | |
SERVER_NAME |
Yes | |
SERVER_PORT |
Yes | |
SERVER_PROTOCOL |
Yes | |
REMOTE_ADDR |
Yes | |
REMOTE_HOST |
Yes | |
REMOTE_USER |
Yes | |
REMOTE_PORT |
Yes | |
REQUEST_METHOD |
Yes | |
REQUEST_FILENAME |
Yes | |
REQUEST_URI |
Yes | |
QUERY_STRING |
Yes | |
SCRIPT_FILENAME |
Yes | |
REQUEST_SCHEME |
Yes | |
THE_REQUEST |
Yes | |
IPV6 |
Yes | |
TIME |
Yes | |
TIME_YEAR |
Yes | |
TIME_MON |
Yes | |
TIME_DAY |
Yes | |
TIME_HOUR |
Yes | |
TIME_MIN |
Yes | |
TIME_SEC |
Yes | |
TIME_WDAY |
Yes |
- This plugin tries to make things as secure as possible. Wherever an unclear situation occurs, access will be denied to prevent unintended access, e.g. if unsupported, security-critical directives are being used (HTTP 500 response). Unsupported, non-security-related directives will be ignored.
- Global configuration within your
http {}
context is technically possible. However, you are encouraged to use this plugin only in theserver {}
contexts that will need it. - To make your life easier, you can create a config snippet and include it in the
server {}
config:server { ... include snippets/htaccess.conf ... }
Using IntelliJ IDEA, you can remotely debug Lua scripts like htaccess.lua
running in an nginx Docker container, using these steps. In particular, this has been tested on a Windows 10 host running IntelliJ IDEA 2022.2.4 (Community Edition), with the fabiocicerchia/nginx-lua:1.23.2-almalinux8.7-20221201
Docker image from https://hub.docker.com/r/fabiocicerchia/nginx-lua.
It assumes you are mapping a host path of C:\path\to\project\on\windows
to a path in the container volume of /path/to/project
.
- Install IntelliJ IDEA
- The container needs to forward port 9966 to allow for Lua debugging. If you are using
docker-compose.yml
it will look something like this:version: '2.4' services: nginx-lua: build: context: . container_name: nginx-lua ports: # For nginx requests over HTTP - 80:80 # If you support nginx requests over HTTPS - 443:443 # For Lua debugging - 9966:9966 volumes: - ./relative/path/to/project/on/windows/:/path/to/project/
- In the
Dockerfile
for the container, run the following command to build the EmmyLuaDebugger from source:RUN dnf install -y cmake && \ curl https://github.com/EmmyLua/EmmyLuaDebugger/archive/refs/tags/1.0.16.tar.gz \ -L -o EmmyLuaDebugger-1.0.16.tar.gz && \ tar -xzvf EmmyLuaDebugger-1.0.16.tar.gz && \ cd EmmyLuaDebugger-1.0.16 && \ mkdir -p build && \ cd build && \ cmake -DCMAKE_BUILD_TYPE=Release ../ && \ make install && \ mkdir -p /usr/local/emmy && \ cp install/bin/emmy_core.so /usr/local/emmy/ && \ cd .. && \ cd .. && \ rm -rf EmmyLuaDebugger-1.0.16 EmmyLuaDebugger-1.0.16.tar.gz
- Start the container
- At the top of your Lua script to debug, add the following:
_G.emmy = {} _G.emmy.fixPath = function(path) return string.gsub(path, '/path/to/project/', 'C:/path/to/project/on/windows') end package.cpath = package.cpath .. ';/usr/local/emmy/?.so' local dbg = require('emmy_core') dbg.tcpListen('localhost', 9966) dbg.waitIDE() dbg.breakHere()
- In IDEA, create a Run/Debug configuration per the link and then start the debugger
When you request a URL that triggers the Lua script, it will pause on the dbg.breakHere()
line so you can step through the code, watch variables, etc.