.htaccess for nginx

April 17, 2026 ยท View on GitHub

.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.

Stop using Apache

  • 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.

Reasons for .htaccess in nginx

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.

Performance

.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.

Requirements

  • Debian or Fedora environment
  • nginx v1.19+ with Lua module
  • curl 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_)

Installation

  1. Install nginx with the Lua module libnginx-mod-http-lua and the luajit and luarocks packages.
    1. Debian: apt-get install nginx libnginx-mod-http-lua luajit luarocks
    2. Fedora: dnf install nginx libnginx-mod-http-lua luajit luarocks
  2. Install the LuaFileSystem module via luarocks install luafilesystem
  3. 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
    
  4. 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
    
  5. Add the following configuration to the nginx http {} context:
    http {
        ...
        lua_shared_dict htaccess 16m;
    
        # This is useful for debugging but may cause performance problems
        # lua_code_cache off;
        ...
    }
    
    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.
  6. 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;
        ...
    }
    

Example

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.

Supported Syntax

The following tables came from this page.

Sections

ModuleSectionSupportedNotes
core<Else>No
core<ElseIf>No
core<Files>Yes
core<FilesMatch>Yes
core<If>No
core<IfDefine>NeverImpossible to be implemented. Apache specific
core<IfDirective>Yes
core<IfFile>Yes
core<IfModule>YesEmulating 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>YesThe version will be simulated as Apache 2.4.0

Directives

Directives not listed below are not supported.

ModuleDirectiveSupportedNotes
coreAcceptPathInfoNo
coreAddDefaultCharsetNo
coreCGIMapExtensionNo
coreCGIPassAuthNo
coreCGIVarNo
coreContentDigestNo
coreDefaultTypeNo
coreEnableMMAPNo
coreEnableSendfileNo
coreErrorDocumentNo
coreFileETagNo
coreForceTypeNo
coreLimitRequestBodyNo
coreLimitXMLRequestBodyNo
coreOptionsNo
coreQualifyRedirectURLNo
coreRLimitCPUNeverRarely used and not practical for nginx
coreRLimitMEMNeverRarely used and not practical for nginx
coreRLimitNPROCNeverRarely used and not practical for nginx
coreScriptInterpreterSourceNo
coreServerSignatureNo
coreSetHandlerNo
coreSetInputFilterNo
coreSetOutputFilterNo
mod_access_compatAllowYesAllow from domainname.tld requires getent command line tool
mod_access_compatDenyYesDeny from domainname.tld requires getent command line tool
mod_access_compatOrderYes
mod_access_compatSatisfyNeverSecurity reasons. Satisfy All assumed
mod_actionsActionNeverSecurity reasons. CGI request handling must be in main host config
mod_aliasRedirectYes
mod_aliasRedirectMatchYes
mod_aliasRedirectPermanentYes
mod_aliasRedirectTempYes
mod_auth_basicAuthBasicAuthoritativeNo
mod_auth_basicAuthBasicFakeNo
mod_auth_basicAuthBasicProviderNo
mod_auth_basicAuthBasicUseDigestAlgorithmNo
mod_auth_digest*No
mod_auth_form*No
mod_authn_anon*No
mod_authn_coreAuthNameYes
mod_authn_coreAuthTypePartiallyOnly AuthType Basic supported
mod_authn_dbm*No
mod_authn_fileAuthUserFileYes
mod_authn_socache*No
mod_authnz_ldap*No
mod_authz_coreAuthMergingNo
mod_authz_coreRequirePartiallyRequire group, host, expr not supported
mod_authz_dbm*No
mod_authz_groupfile*No
mod_autoindexAddAltNo
mod_autoindexAddAltByEncodingNo
mod_autoindexAddAltByTypeNo
mod_autoindexAddDescriptionNo
mod_autoindexAddIconNo
mod_autoindexAddIconByEncodingNo
mod_autoindexAddIconByTypeNo
mod_autoindexDefaultIconNo
mod_autoindexHeaderNameNo
mod_autoindexIndexHeadInsertNo
mod_autoindexIndexIgnoreNo
mod_autoindexIndexIgnoreResetNo
mod_autoindexIndexOptionsNo
mod_autoindexIndexOrderDefaultNo
mod_autoindexIndexStyleSheetNo
mod_autoindexReadmeNameNo
mod_cern_meta*NoRarely used
mod_charset_liteCharsetDefaultNo
mod_charset_liteCharsetOptionsNo
mod_charset_liteCharsetSourceEncNo
mod_dirDirectoryCheckHandlerNo
mod_dirDirectoryIndexNo
mod_dirDirectoryIndexRedirectNo
mod_dirDirectorySlashNo
mod_dirFallbackResourceNo
mod_envPassEnvNo
mod_envSetEnvNo
mod_envUnsetEnvNo
mod_expiresExpiresActiveYes
mod_expiresExpiresByTypeYesBoth long form ("access plus 1 year") and compact form ("A3600"/"M3600") are supported
mod_expiresExpiresDefaultYes
mod_filterAddOutputFilterByTypeNo
mod_filterFilterChainNo
mod_filterFilterDeclareNo
mod_filterFilterProtocolNo
mod_filterFilterProviderNo
mod_headersHeaderYes
mod_headersRequestHeaderNo
mod_imagemap*No
mod_includeSSIErrorMsgNo
mod_includeSSITimeFormatNo
mod_includeSSIUndefinedEchoNo
mod_includeXBitHackNo
mod_isapi*No
mod_ldap*No
mod_logio*No
mod_lua*No
mod_mimeAddCharsetNo
mod_mimeAddEncodingNo
mod_mimeAddHandlerNo
mod_mimeAddInputFilterNo
mod_mimeAddLanguageNo
mod_mimeAddOutputFilterNo
mod_mimeAddTypeYes
mod_mimeDefaultLanguageNo
mod_mimeMultiviewsMatchNo
mod_mimeRemoveCharsetNo
mod_mimeRemoveEncodingNo
mod_mimeRemoveHandlerNo
mod_mimeRemoveInputFilterNo
mod_mimeRemoveLanguageNo
mod_mimeRemoveOutputFilterNo
mod_mimeRemoveTypeNo
mod_negotiationForceLanguagePriorityNo
mod_negotiationLanguagePriorityNo
mod_reflector*NeverSecurity reasons
mod_rewriteRewriteBaseYes
mod_rewriteRewriteCondPartialEnvironment (E=) flag is unsupported, as are CondPattern integer comparisons and some file attribute tests listed in the documentation.
mod_rewriteRewriteEngineYes
mod_rewriteRewriteOptionsNo
mod_rewriteRewriteRuleYes
mod_session*No
mod_setenvifBrowserMatchNo
mod_setenvifBrowserMatchNoCaseNo
mod_setenvifSetEnvIfNo
mod_setenvifSetEnvIfExprNo
mod_setenvifSetEnvIfNoCaseNo
mod_spelingCheckCaseOnlyNo
mod_spelingCheckSpellingNo
mod_sslSSLCipherSuiteNo
mod_sslSSLOptionsNo
mod_sslSSLRenegBufferSizeNo
mod_sslSSLRequireNo
mod_sslSSLRequireSSLNo
mod_sslSSLUserNameNo
mod_sslSSLVerifyClientNo
mod_sslSSLVerifyDepthNo
mod_substituteSubstituteNo
mod_substituteSubstituteInheritBeforeNo
mod_substituteSubstituteMaxLineLengthNo
mod_usertrackCookieDomainNo
mod_usertrackCookieExpiresNo
mod_usertrackCookieHTTPOnlyNo
mod_usertrackCookieNameNo
mod_usertrackCookieSameSiteNo
mod_usertrackCookieSecureNo
mod_usertrackCookieStyleNo
mod_usertrackCookieTrackingNo

Variables

Variables not listed below are not supported.

VariableSupportedNotes
HTTP_*Yesall standard and non-standard HTTP header fields are supported
HTTPSYes
DOCUMENT_ROOTYes
SERVER_ADDRYes
SERVER_NAMEYes
SERVER_PORTYes
SERVER_PROTOCOLYes
REMOTE_ADDRYes
REMOTE_HOSTYes
REMOTE_USERYes
REMOTE_PORTYes
REQUEST_METHODYes
REQUEST_FILENAMEYes
REQUEST_URIYes
QUERY_STRINGYes
SCRIPT_FILENAMEYes
REQUEST_SCHEMEYes
THE_REQUESTYes
IPV6Yes
TIMEYes
TIME_YEARYes
TIME_MONYes
TIME_DAYYes
TIME_HOURYes
TIME_MINYes
TIME_SECYes
TIME_WDAYYes

Tips

  • 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 the server {} 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
        ...
    }