Soubor .htaccess obsahuje pravidla webového serveru Apache pro jeden adresář vašeho webu: přesměrování, blokování souborů, ochranu heslem, cache a kompresi. Funguje jen tehdy, když požadavek skutečně zpracovává Apache, a nginx ho úplně ignoruje. Níže najdete pravidla, která jsme vyzkoušeli na testovací sestavě s nginx před Apache (takové uspořádání používá mnoho hostingových panelů): každé s hotovým kódem, skutečnou odpovědí curl -I a pastí, kvůli které může potichu selhat.
Jak jsme testovali
10. listopadu 2026 na testovací sestavě: kontejner Ubuntu 24.04, nginx 1.24.0 před Apache 2.4.58 (mpm_event, AllowOverride All), PHP-FPM 8.3.6, MariaDB 10.11, WordPress 6.4.3 s čitelnými trvalými odkazy. Konfigurace nginx odpovídá typické šabloně hostingového panelu: nginx sám obsluhuje statické soubory ze seznamu přípon a vše ostatní předává Apache. Vyzkoušeli jsme také nginx bez Apache a proxy, která s Apache komunikuje prostým HTTP. Každé pravidlo jsme ověřili přes curl, rychlost jsme měřili nástrojem ab. Výsledky popisují testovací sestavu, ne servery RichHost.
Kde .htaccess funguje a kde se ignoruje
Apache hledá .htaccess v každém adresáři na cestě k požadovanému souboru a při každém požadavku ho čte znovu. Proto se změny projeví okamžitě bez restartu a proto jediná syntaktická chyba shodí celý web: na testovací sestavě vracel překlep na každé URL chybu 500 Internal Server Error. Soubor leží v kořenovém adresáři webu (obvykle public_html domény) a správci souborů ho často skrývají, protože název začíná tečkou.
Hlavní past: nginx .htaccess nečte. Na serveru jen s nginx naše blokovací pravidla nic nedělala a sestava vydala .env i kopii wp-config.php.bak s heslem k databázi. S nginx a Apache je to záludnější. Typická šablona panelu nechává nginx přímo obsluhovat soubory s příponami jako jpg, png, webp, css, js, txt, pdf, zip, html, htm a několika desítkami dalších. Tyto požadavky se k Apache nikdy nedostanou, takže pro ně .htaccess neexistuje. Na sestavě to přineslo čtyři překvapení:
- přesměrování z existujícího souboru old-page.html nic nedělalo, nginx vracel starý soubor s kódem 200;
- heslo na adresáři chránilo index.php, ale report.html ve stejném adresáři se otevřel bez hesla;
- blokovací pravidlo pro *.zip vracelo 403 z Apache, ale archiv se přes nginx bez problému stáhl;
- pravidla cache pro obrázky se nepoužila, protože nginx sám nastavuje
Cache-Control: max-age=315360000.
Odtud rychlý test: pokud odpověď pro soubor obsahuje max-age=315360000 a datum Expires v roce 2037, obsloužil ho nginx a vaše pravidla v .htaccess ho nikdy neviděla.
Jak ověřit jakékoli pravidlo
Před úpravou si uložte kopii (cp .htaccess .htaccess.bak) a po každé změně kontrolujte hlavičky odpovědi, ne prohlížeč, který si přesměrování 301 ukládá do cache:
curl -I http://example.com/ curl -IL http://www.example.com/page/
Přepínač -L projde celý řetězec přesměrování. Pokud najednou každá stránka vrací 500, obnovte kopii a otevřete chybový log domény: Apache do něj zapisuje přesný důvod, například Invalid command 'Order', perhaps misspelled or defined by a module not included in the server configuration.
Přesměrování
Z HTTP na HTTPS a smyčka za proxy
Klasické pravidlo z většiny návodů:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Přes HTTP funguje: HTTP/1.1 301, Location: https://example.com/hello.php. V takové šabloně nginx předává požadavky HTTPS do Apache také přes HTTPS, takže Apache vidí zabezpečené spojení a odpoví 200. Pokud ale TLS ukončuje proxy a s Apache mluví prostým HTTP (Cloudflare v režimu Flexible, load balancer), Apache považuje každý požadavek za nezabezpečený a přesměrovává na stejnou URL. Na sestavě se curl zastavil s hlášením Maximum (50) redirects followed a prohlížeč by ukázal ERR_TOO_MANY_REDIRECTS. Řešením je kontrolovat hlavičku, ve které proxy předává původní protokol.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
S tímto pravidlem odpověděly všechny tři konfigurace sestavy správně: HTTP dostalo 301, obě varianty HTTPS 200. Parametry dotazu se zachovají automaticky: ?utm=1&b=2 dorazilo na adresu HTTPS beze změny. V mnoha panelech můžete také zapnout přesměrování na HTTPS v nastavení SSL domény; pak to zařídí nginx a žádné pravidlo v .htaccess nepotřebujete.
www a bez www jedním přesměrováním
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Na pořadí záleží. V této podobě se http://www.example.com/sample-page/ dostal na https://example.com/sample-page/ jediným přesměrováním. Když bylo pravidlo HTTPS první, řetězec se prodloužil na dva skoky. Opačný směr, z varianty bez www na www:
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
301 pro jednu stránku
Redirect 301 /old-page.html https://example.com/new-page.html Redirect 301 /old-post/ https://example.com/hello-world/
Oba řádky vrátily 301 na novou URL, když stály nad blokem WordPressu. Stejné přesměrování zapsané jako RewriteRule pod blokem nezabralo: WordPress adresu zachytil a vrátil 404. A pamatujte na statické soubory: dokud old-page.html ležel na disku, nginx ho vracel s kódem 200 a přesměrování začalo fungovat až po smazání souboru. Další vzory včetně hromadných přesunů a změny domény najdete v našem návodu k přesměrování 301 v .htaccess a Nginx.
Lomítko na konci
WordPress nic dalšího nepotřebuje: sám přesměroval /sample-page na /sample-page/. Oblíbené pravidlo „odstranit koncové lomítko“ v kombinaci s takovými odkazy vytvořilo nekonečnou smyčku: .htaccess lomítko odebral, WordPress ho přidal zpět. Pro weby bez WordPressu, jejichž URL mají končit lomítkem:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1/ [R=301,L]
Na sestavě šlo /catalog/shoes?color=red na /catalog/shoes/?color=red, zatímco skutečných souborů jako /hello.php se pravidlo nedotklo.
Výchozí blok WordPressu
WordPress 6.4 tento blok zapíše sám, když uložíte nastavení trvalých odkazů. Bez něj vracela /hello-world/ na sestavě 404 z Apache, s ním 200.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Všechna vlastní pravidla dávejte nad # BEGIN WordPress: WordPress přepisuje vše mezi značkami a pravidlo pod blokem nikdy neuvidí požadavek, který neodpovídá skutečnému souboru.
Bezpečnost: blokování souborů, povolené IP adresy, hesla
Blokování přístupu k souborům
<FilesMatch "^(\.env|wp-config\.php|xmlrpc\.php)$"> Require all denied </FilesMatch> <FilesMatch "\.(bak|old|orig|sql|zip|tar|gz|log)$"> Require all denied </FilesMatch>
Bez těchto pravidel Apache vydal .env s heslem k databázi a wp-config.php.bak se stáhl jako obyčejný soubor i s řádkem DB_PASSWORD. Samotný wp-config.php vracel 200 s prázdnou stránkou: PHP ho spustí a nic nevypíše, takže skutečné nebezpečí představují kopie s jinou příponou. S pravidly vracely všechny tyto URL 403, i xmlrpc.php, včetně požadavků POST. Výjimkou jsou opět statické soubory: backup.zip se přes nginx dál stahoval, takže archivy ani výpisy databáze v public_html vůbec nenechávejte.
Stará syntaxe Apache 2.2 (Order Allow,Deny a Deny from all) funguje jen s modulem mod_access_compat. Když jsme ho vypnuli, stejný soubor způsobil chybu 500 na každé stránce webu, proto používejte Require all denied.
Přístup jen z vaší IP
<Files wp-login.php> Require ip 203.0.113.10 </Files>
Za nginx vidí Apache adresu proxy. Bez mod_remoteip blokovalo pravidlo s IP klienta na sestavě všechny, zatímco Require ip 127.0.0.1 pustilo dovnitř každého. Typická instalace panelu zapíná mod_remoteip s hlavičkou X-Real-IP a v tomto uspořádání pravidlo fungovalo přesně: povolená adresa dostala 200, jiná 403 a podvržená hlavička X-Real-IP nepomohla, protože ji nginx přepíše.
Ochrana heslem přes .htpasswd
htpasswd -c -B /home/user/web/example.com/private/.htpasswd admin
AuthType Basic AuthName "Restricted area" AuthUserFile /home/user/web/example.com/private/.htpasswd Require valid-user
Soubor s hesly ukládejte mimo public_html a uvádějte plnou cestu. Apache odpověděl 401 Unauthorized s hlavičkou WWW-Authenticate: Basic realm="Restricted area" a se správným heslem 200. Přes nginx se ale report.html v chráněném adresáři otevřel bez hesla. Pokud adresář obsahuje statické soubory, musí heslo kontrolovat nginx: to nastaví správce serveru v konfiguraci nginx. Na sdíleném hostingu se proto obraťte na podporu svého poskytovatele.
Cache a komprese
<IfModule mod_expires.c> ExpiresActive On ExpiresByType image/webp "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType text/javascript "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" </IfModule> <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json image/svg+xml </IfModule>
Apache přidal k PNG Cache-Control: max-age=31536000 a k jquery.min.js 30 dní. Pozor na typ JavaScriptu: v Ubuntu 24.04 se soubory .js posílají jako text/javascript, takže pravidlo jen s application/javascript je mine. Komprese přinesla zřetelný rozdíl: jquery.min.js se zmenšil z 87 553 na 30 372 bajtů, CSS bloků WordPressu ze 110 147 na 14 472 a HTML úvodní stránky z 83 776 na 14 533. S nginx vpředu se oba bloky týkají jen toho, co obsluhuje Apache, tedy HTML: obrázky, CSS a JS cachuje a komprimuje nginx.
Vlastní chybové stránky
ErrorDocument 404 /404.html ErrorDocument 403 /403.html
Uvádějte cestu, ne celou URL: s ErrorDocument 404 https://example.com/404.html odpověděl Apache 302 Found místo 404, takže vyhledávače vidí přesměrování místo chyby. U neexistujících souborů .php se stránka nepoužila, protože PHP-FPM sám vrací „File not found.“. Ve WordPressu stránky 404 vykresluje šablona, takže tam je ErrorDocument 404 zbytečný.
Nastavení PHP: php_value, nebo .user.ini
Řádek php_value upload_max_filesize 64M v .htaccess funguje jen s mod_php. Pod PHP-FPM způsobil chybu 500 na každé stránce s hlášením Invalid command 'php_value'. Obalení do <IfModule mod_php.c> chybu odstraní, hodnota se ale nezmění. Správné místo je soubor .user.ini v kořenovém adresáři webu.
upload_max_filesize = 64M post_max_size = 64M memory_limit = 256M
PHP nové hodnoty nepřevzalo hned: user_ini.cache_ttl má ve výchozím stavu 300 sekund a po úpravě sestava ukazovala starou hodnotu ještě dalších 206 sekund. .user.ini funguje stejně i na serveru jen s nginx. Pokud zvýšení limitu paměti nepomůže, hledejte příčinu v kódu, viz náš návod k chybě Allowed memory size exhausted.
Zpomaluje .htaccess web?
Porovnali jsme Apache s AllowOverride None a AllowOverride All pomocí ab (16 souběžných požadavků, pět střídavých kol, medián). Soubor .htaccess měl 37 řádků: www a HTTPS, přesměrování, dva blokovací bloky, cache, kompresi, ErrorDocument a blok WordPressu.
| Konfigurace Apache | Statický soubor, pož./s | Malý PHP skript, pož./s |
|---|---|---|
| AllowOverride None | 26 301 | 7 417 |
| Stejných 37 řádků v konfiguraci serveru | 24 267 | 7 614 |
| AllowOverride All s .htaccess o 37 řádcích | 14 817 | 6 080 |
Samotné povolení .htaccess nestojí skoro nic: v samostatném kole obsloužil Apache s AllowOverride All, ale bez takových souborů 24 268 požadavků za sekundu oproti 24 356 s None. Stojí parsování: Apache čte a zpracovává .htaccess znovu při každém požadavku, takže propustnost pro statické soubory klesla zhruba o 44 %, zatímco stejná pravidla v konfiguraci serveru stála asi 8 %. To je přibližně 26 mikrosekund na požadavek na sestavě se 2 vCPU, zatímco vygenerování stránky WordPressu trvalo asi 85 milisekund, takže u dynamických stránek rozdíl mizí. Během měření běžely v kontejneru i jiné testy, absolutní čísla jsou proto zašuměná; porovnávejte jen poměry.
Závěr: na sdíleném hostingu se .htaccess nevzdávejte, ale odstraňte pravidla, která dublují to, co už dělá nginx nebo ovládací panel (cache statických souborů, HTTPS, blokování skrytých souborů).
Tahák: úkol, pravidlo, kontrola
| Úkol | Pravidlo | Jak ověřit |
|---|---|---|
| Z HTTP na HTTPS | RewriteCond %{HTTPS} off plus podmínka X-Forwarded-Proto |
curl -I http://…: 301 na https; curl -IL https://…: 200, bez smyčky |
| Z www na bez www | RewriteCond %{HTTP_HOST} ^www\.(.+)$ |
curl -IL http://www…: jediné 301 |
| Přesun jedné stránky | Redirect 301 /old/ https://…/new/ nad blokem WordPressu |
301 se správným Location; starý soubor smazán |
| Lomítko na konci | Ve WordPressu nic, jinde pravidlo s !-f |
curl -IL, bez smyčky |
| Blokace .env, záloh, xmlrpc.php | FilesMatch s Require all denied |
403; archivy ověřit zvlášť přes nginx |
| Administrace jen z vaší IP | Require ip plus mod_remoteip na serveru |
200 z vaší sítě, 403 z mobilních dat |
| Heslo na adresáři | AuthType Basic, .htpasswd mimo public_html | 401 bez hesla, i pro .html a .pdf |
| Cache statických souborů | mod_expires |
Cache-Control v curl -I souboru |
| Komprese | mod_deflate |
curl -I -H 'Accept-Encoding: gzip': Content-Encoding: gzip |
| Chybové stránky | ErrorDocument 404 /404.html |
Stav 404, ne 302 |
| Limity PHP | .user.ini místo php_value | phpinfo() nebo ini_get() po 5 minutách |
Časté dotazy
Proč web po úpravě .htaccess ukazuje chybu 500?
Apache nedokázal zpracovat některý řádek: překlep, direktivu vypnutého modulu (php_value, Order) nebo zatoulaný znak. Obnovte kopii; přesný řádek najdete v chybovém logu.
Potřebuji .htaccess na serveru jen s nginx?
Ne, nginx ho nečte. Stejné úkoly se řeší v konfiguraci nginx, kterou na sdíleném hostingu mění administrátor nebo ovládací panel.
Jaká oprávnění má mít .htaccess?
Stačí 644: vlastník zapisuje, webový server čte. 777 není nikdy potřeba. Pokud Apache soubor vůbec nepřečte, celý web vrací chybu 403 Forbidden a v logu je pcfg_openfile: unable to check htaccess file.
Proč curl ukazuje přesměrování, ale prohlížeč otevře starou stránku?
Prohlížeče si přesměrování 301 pamatují. Testujte v anonymním okně nebo vymažte cache a při ladění používejte R=302.
Weby na RichHost spravujete ve vlastním ovládacím panelu s asistentem AI a certifikát Let’s Encrypt je zdarma. Tarify najdete na stránce s ceníkem hostingu.



