Most hosting panels still run Apache underneath, which means your .htaccess rules keep working without modification. Nginx sits at the front on ports 80 and 443, terminates SSL, and proxies requests back to Apache on a loopback address. Apache processes the request, reads your .htaccess, and returns a response. Nothing about your existing rewrite logic breaks.
But if you are moving to a pure-nginx setup — for performance, for simpler configuration management, or because your previous panel did not bundle Apache — you need to translate those rules. The nginx config format is fundamentally different from Apache directives, and most translation guides online skip the edge cases that actually trip people up.
This cheat sheet covers fifteen common patterns: redirects, rewrites, access control, headers, caching, and file blocking. After that: WordPress, Laravel, Symfony, and Drupal default configurations in nginx form. Then the directives that do not have a direct nginx equivalent and need a different approach entirely. Finally, a brief section on how Panelica handles per-domain web server mode selection, and what that means for your .htaccess files specifically.
Tested syntax. Copy-pasteable. No guesswork.
Jump to section: For Site Developers | For Sysadmins | 15 Conversion Patterns | Framework Defaults | Directives Without Equivalents | Per-Domain Web Server in Panelica
For Site Developers
First: do you actually need to convert anything?
The first question to ask when moving to a new hosting panel is not "how do I convert my .htaccess?" It is "does Apache still run on this server?"
Many modern panels, including Panelica by default, run in a hybrid mode: nginx listens on ports 80 and 443, handles SSL termination, and passes requests to Apache running on a local port (127.0.0.1:7080). Apache processes the request with AllowOverride All enabled, which means your .htaccess files are read and applied exactly as they would be on a traditional Apache setup. From your application's perspective, nothing changed.
If the panel runs in this hybrid mode, your WordPress, Laravel, Drupal, or CodeIgniter .htaccess files work immediately after migration. No translation required.
When conversion becomes necessary
Conversion is only required in two scenarios:
- Pure nginx mode — the panel is configured to serve your domain directly through nginx with no Apache backend. Your
.htaccessfiles exist on disk but are never read. - You want to move rewrite logic out of
.htaccessand into the vhost config for performance reasons (more on that in the sysadmin section).
If you are on hybrid mode and your application is not behaving correctly after migration, the issue is almost certainly not .htaccess — it is more likely a document root mismatch, a PHP version mismatch, or a database connection issue.
WordPress default rewrite rules in nginx form
WordPress generates its default .htaccess when you set up pretty permalinks. In pure nginx mode, replace it with this location block:
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/opt/panelica/var/run/php-fpm-8.4.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
The try_files directive is the nginx equivalent of the WordPress RewriteRule block: check if a real file exists, then check if a real directory exists, and fall back to index.php with the query string passed through. Common gotcha: WordPress needs $args (not $query_string) as the fallback parameter — using the wrong variable breaks WP-CLI and some REST API calls.
Laravel public/.htaccess in nginx form
Laravel's front controller pattern is straightforward in nginx:
root /var/www/laravel/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/opt/panelica/var/run/php-fpm-8.4.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
Laravel-specific note: the .htaccess also includes a trailing-slash removal rule. In nginx, add this if your application relies on it:
rewrite ^/(.*)/$ /$1 permanent;
Common gotcha: if you use Laravel's route caching (php artisan route:cache), make sure PHP-FPM is using the correct PHP version. Route caching with a different PHP binary than what serves requests causes serialization mismatches.
Symfony front controller in nginx form
root /var/www/symfony/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/opt/panelica/var/run/php-fpm-8.4.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
include fastcgi_params;
}
location ~ \.php$ {
return 404;
}
The internal directive prevents direct browser access to PHP files while still allowing nginx to serve them via the front controller. The catch-all location ~ \.php$ { return 404; } below it blocks any PHP file that does not match the front controller pattern.
Drupal clean URLs in nginx form
location / {
try_files $uri /index.php?$query_string;
}
location @rewrite {
rewrite ^ /index.php;
}
location ~ /vendor/.*\.php$ {
deny all;
return 404;
}
location ~ '\.php$|^/update.php' {
fastcgi_pass unix:/opt/panelica/var/run/php-fpm-8.4.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
try_files $uri @rewrite;
expires max;
log_not_found off;
}
Drupal requires blocking access to the vendor/ directory explicitly. In Apache, .htaccess files inside vendor handle this. nginx has no per-directory config mechanism, so the vhost config must cover it.
PHP value overrides: where they actually live in an nginx setup
One of the most common migration surprises: php_value and php_admin_value in .htaccess do not have a direct nginx equivalent. Nginx does not interpret PHP directives — PHP-FPM does. On a PHP-FPM setup (which is every modern hosting panel), these values go in the PHP-FPM pool configuration file, not in the nginx vhost.
In Panelica, PHP values for a domain can be set from the panel interface under domain settings, PHP configuration. The panel writes them to the per-domain, per-user PHP-FPM pool file and reloads FPM automatically. No manual config editing required.
For Sysadmins
The core architectural difference
The single most important thing to understand when converting .htaccess rules to nginx is the loading model:
- Apache with .htaccess: configuration files are read on every request. Apache walks the directory tree from the document root to the requested file, reading and applying any
.htaccessit finds. This is flexible but costs filesystem operations on each request. - nginx: configuration is loaded at startup and reloaded only on explicit signal (
nginx -s reloador SIGHUP). There is no per-directory config file concept. All rules live in the vhost configuration block.
This difference explains both the performance advantage of nginx and its inflexibility for shared hosting environments: you cannot let untrusted users write their own nginx config. Apache's .htaccess mechanism exists precisely to allow per-directory config without root access. This is why hybrid mode (nginx front, Apache back) remains the default on multi-tenant hosting panels: you get nginx's SSL termination performance at the edge while preserving per-user configuration flexibility via .htaccess.
Why nginx is faster (and when it matters)
Moving rewrite logic out of .htaccess and into the nginx vhost eliminates the filesystem read overhead on every request. For a site in a deep directory tree with multiple .htaccess files at different levels, this overhead is measurable — typically 1 to 5 milliseconds per request depending on disk I/O latency.
In practice: for most production workloads, the difference is imperceptible. For high-traffic sites serving thousands of requests per second, it matters. For single-tenant VPS setups with controlled environments, moving to nginx_only mode and consolidating rules in the vhost is a clean, lower-overhead configuration.
15 Conversion Patterns
Each pattern shows the Apache .htaccess directive on the left and the nginx vhost equivalent on the right. These go inside a server { ... } block unless otherwise noted.
Pattern 1 — HTTPS redirect
Redirect all HTTP traffic to HTTPS. In nginx, this is cleanest as a separate server block listening on port 80.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L]
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Pattern 2 — WWW redirect (non-www to www)
Enforce the www subdomain with a permanent redirect.
RewriteCond %{HTTP_HOST} ^example\.com$
RewriteRule (.*) https://www.example.com/$1 [R=301,L]
server {
server_name example.com;
return 301 https://www.example.com$request_uri;
}
Pattern 3 — Front controller (WordPress / Laravel / CodeIgniter)
Route all requests through index.php if no real file or directory matches. This is the core rewrite for every PHP framework using pretty URLs.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [L]
location / {
try_files $uri $uri/ /index.php?$args;
}
Pattern 4 — Permanent 301 redirect for a specific path
Redirect a renamed page with a permanent redirect that search engines will follow.
RewriteRule ^old-page$ /new-page [R=301,L]
rewrite ^/old-page$ /new-page permanent;
Pattern 5 — IP-based access control
Block a specific IP address while allowing everyone else.
Order Deny,Allow
Deny from 192.168.1.10
Allow from all
location / {
deny 192.168.1.10;
allow all;
}
Pattern 6 — Block sensitive file extensions
Deny access to backup files, SQL dumps, and environment files that should never be served publicly.
<FilesMatch "\.(bak|sql|env)$">
Require all denied
</FilesMatch>
location ~ \.(bak|sql|env)$ {
deny all;
}
Pattern 7 — Disable directory listing
Prevent nginx from showing directory contents when no index file is present. nginx disables autoindex by default; this makes it explicit.
Options -Indexes
autoindex off;
Pattern 8 — Custom error pages
Serve custom error pages instead of nginx's default error responses.
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html
error_page 404 /errors/404.html;
error_page 500 /errors/500.html;
Pattern 9 — Security response headers
Add HTTP security headers. The always keyword ensures headers are sent even on error responses.
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header set Strict-Transport-Security "max-age=31536000"
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header Strict-Transport-Security "max-age=31536000" always;
Pattern 10 — Hotlink protection
Block other websites from embedding your images directly. The valid_referers directive handles this in nginx.
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ - [F,L]
location ~* \.(jpg|jpeg|png|gif)$ {
valid_referers none blocked
example.com *.example.com;
if ($invalid_referer) {
return 403;
}
}
Pattern 11 — Trailing slash enforcement
Force a trailing slash on directory URLs with a permanent redirect.
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+[^/])$ /$1/ [R=301,L]
location / {
if (-d $request_filename) {
rewrite ^(.+[^/])$ $1/ permanent;
}
}
Pattern 12 — Long-term cache headers for static assets
Instruct browsers to cache images for a year and CSS for a month.
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
</IfModule>
location ~* \.(jpg|jpeg|png|gif|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.css$ {
expires 1M;
}
Pattern 13 — PHP value overrides
This pattern does not translate to nginx. PHP-FPM handles PHP directives — the values go in the PHP-FPM pool configuration file, not the nginx vhost.
php_value upload_max_filesize 64M
php_value post_max_size 64M
; In the per-domain PHP-FPM pool .conf file:
php_value[upload_max_filesize] = 64M
php_value[post_max_size] = 64M
; Reload PHP-FPM after editing.
Pattern 14 — Gzip compression
Enable output compression. In nginx, gzip settings typically go in the http block but can be overridden per server block.
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE
text/html text/css
application/javascript
</IfModule>
gzip on;
gzip_types text/html text/css
application/javascript
application/json;
gzip_min_length 1024;
Pattern 15 — Block access to sensitive files by name
Deny direct browser access to .htaccess itself and application configuration files.
<Files .htaccess>
Require all denied
</Files>
<Files wp-config.php>
Require all denied
</Files>
location ~ /\.ht {
deny all;
}
location ~ /wp-config\.php {
deny all;
}
Directives Without a Direct nginx Equivalent
Some Apache directives do not have a one-to-one translation. They need a different tool entirely:
| Apache directive | Why it cannot be directly translated | nginx approach |
|---|---|---|
php_value / php_admin_value |
nginx does not pass PHP directives; PHP-FPM handles them | Set in PHP-FPM pool .conf file: php_value[key] = val |
AllowOverride All |
The concept does not exist in nginx; nginx has no per-directory config file mechanism | All rules go in the server block; per-directory config is not supported |
ModSecurity inline rules |
Rule syntax is compatible but requires nginx ModSecurity module compiled in | nginx ModSecurity (libmodsecurity) is available; rules load from separate .conf files |
mod_pagespeed directives |
Google PageSpeed requires the nginx-pagespeed module compiled at build time | Not in standard nginx builds; use a CDN or application-level asset optimization |
<Limit POST> |
Apache's Limit blocks restrict HTTP methods in a directory context | Use if ($request_method = POST) { return 405; } in the nginx location block |
SetEnvIf |
Apache's conditional environment variable setting has no direct parallel | Use nginx map blocks or the geo module for conditional variable logic |
How Panelica Handles Per-Domain Web Server Mode
Panelica stores a web_server field on each domain record. Three values are supported:
| Mode | Architecture | .htaccess behaviour |
|---|---|---|
| nginx_apache (default) | Nginx on 80/443, Apache on 127.0.0.1:7080 with AllowOverride All | .htaccess files are read and applied natively. No conversion needed. |
| nginx_only | Nginx serves the domain directly, no Apache backend | .htaccess files exist on disk but are never read. Rewrite rules must go in the nginx vhost config. |
| apache_only | Apache serves the domain directly on its own port | .htaccess files are read and applied natively, identical to a traditional Apache setup. |
In hybrid mode, the Apache backend vhost is configured with AllowOverride All and includes correct X-Forwarded-Proto passthrough so that HTTPS detection works correctly from within the application. This is a common failure point when moving from a direct Apache setup to a proxied one — applications check $_SERVER['HTTPS'] and get an empty value because they are no longer the process that terminated SSL.
Changing a domain's web server mode in the panel triggers vhost regeneration for both nginx and Apache (as applicable) and reloads the affected services automatically. No manual config editing or service restarts needed.
For nginx_only mode, PHP value overrides that previously lived in .htaccess can be set from the panel's domain settings under PHP configuration. The panel writes them to the per-domain PHP-FPM pool and reloads FPM — no manual pool file editing required.
If your application uses a directive that does not appear in this cheat sheet, post it on forum.panelica.com. We extend this list with verified conversions as they come up from real migration scenarios.
Related Reading
- Free VPS Control Panels in 2026: An Honest Comparison for Server Owners
- DV, OV, and EV SSL Certificates Explained: Which One Your Hosting Panel Issues
- DDoS Protection on Hosting Servers: What Your Panel Handles and What It Does Not
- WordPress Staging Environments From Your Hosting Panel: One-Click Clones Without the Guesswork
- See how Panelica compares to other panels
Apache and nginx mapping examples tested against Apache 2.4 and nginx 1.27 syntax (2026 stable releases). Panelica web server mode field verified from source (models/domain.go: web_server field, default nginx_apache). Framework default .htaccess configurations cross-referenced with each framework repository at the time of writing. Apache vhost template with AllowOverride All verified from etc/templates/apache/vhost.tpl.