---
title: "Running WordPress on nginx Without Apache"
description: "Turning Proxy mode off removes Apache from your site entirely. Faster and lighter, at the cost of .htaccess. What breaks, what replaces it, and who should not do it."
url: "https://docs.noiz.ie/plesk/running-wordpress-on-nginx-without-apache/"
section: "Plesk"
categories:
  - "Control Panels > Plesk > Administration"
tags:
  - "nginx"
  - "wordpress"
  - "htaccess"
  - "performance"
  - "plesk"
last_reviewed: "4 August 2026, against the current Plesk Obsidian release"
---

# Running WordPress on nginx Without Apache

Most sites on the Noiz platform run two web servers: nginx at the front and Apache behind it. You can remove Apache from a single site's request path, so nginx serves everything and talks to PHP-FPM directly. One web server instead of two.

That is a genuine performance tier, and it is how most specialist WordPress hosting is built. It also has one real cost, and this guide states it before anything else: **nginx does not read `.htaccess`**. This is the honest version of that trade, what replaces each rule you lose, and who should not take it.

**Last reviewed:** 4 August 2026, against the current Plesk Obsidian release. The configuration in this guide was verified on a live WordPress site running with no Apache in the request path. This guide is written for Noiz hosting and is kept current against Plesk, nginx and WordPress. It complements, and does not replace, the official documentation linked below.

### Official Documentation Reference

- [nginx-only hosting (Plesk administrator guide)](https://docs.plesk.com/en-US/obsidian/administrator-guide/website-management/websites-and-domains/hosting-settings/web-server-settings/apache-and-nginx-settings.72320/#nginx-only-hosting): the vendor reference, including its two documented limitations.
- [WordPress on nginx (WordPress Advanced Administration Handbook)](https://developer.wordpress.org/advanced-administration/server/web-server/nginx/): WordPress's own nginx configuration, including the permalink rules.
- [nginx `try_files`](https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files) and [`location`](https://nginx.org/en/docs/http/ngx_http_core_module.html#location): the directives that do the work, and the matching order that decides which rule wins.
- [Protecting your uploads directory (WooCommerce)](https://woocommerce.com/document/digital-downloadable-product-handling/): WooCommerce's own statement about what nginx needs for downloadable products.

## Prerequisites

- A WordPress site on a Noiz plan whose control panel is Plesk.
- [The recommended web server configuration on Noiz hosting](/plesk/the-recommended-web-server-configuration-on-noiz-hosting/), if the two-web-server model is new to you. This guide assumes it.
- A recent backup. This change is reversible in seconds, but the point of a backup is that you do not have to trust that sentence.
- A list of what is in your site's `.htaccess`. Everything in this guide follows from that file.

## What actually changes

On the **Apache & nginx Settings** page for the domain, **Proxy mode** is cleared. One tick, and Apache leaves this site's request path.

It is not the only setting that has to change, though, and the second one is not optional. **Serve static files directly by nginx** must be cleared at the same time, or the site's custom nginx rules stop being applied and nothing tells you. That is covered in full two sections below, and it is worth reading before you touch either checkbox.

![The nginx settings section of the Plesk Apache and nginx Settings page with the Proxy mode checkbox cleared, above Plesk's caption reading Nginx proxies requests to Apache, turn off to stop using Apache](/images/kb/142_plesk-proxy-mode-off.png)

Plesk's own description of the checkbox is one sentence: *"Nginx proxies requests to Apache. Turn off to stop using Apache."*

After that, nginx receives the request, serves static files from disk itself, and passes PHP straight to PHP-FPM. Apache is still installed and still runs the panel's own services, but it is no longer in the path for your site. Plesk supports this explicitly:

> You can configure individual websites to be served only by nginx without stopping or disabling Apache, and it does not impact websites hosted using Apache in any way.

It is per-site, so a server can run a mix indefinitely. There is no flag day.

Plesk documents two other limitations: SSI, Perl and Python support become unavailable, and the PHP handler is restricted to the nginx-served options. See [PHP handlers on Noiz hosting](/plesk/php-handlers-explained-fpm-served-by-apache-or-nginx/).

## What stops working

`.htaccess` becomes an inert text file. Not partly. Entirely.

Every rule in it stops applying: permalink rewrites, redirects, deny rules, expiry headers, hotlink protection, security-plugin blocks and cache-plugin page serving. WordPress's own documentation is direct about the reason:

> With Nginx there is no directory-level configuration file like Apache's `.htaccess` or IIS's `web.config` files. All configuration has to be done at the server level by an administrator, and WordPress cannot modify the configuration, like it can with Apache or IIS.

The part that catches people is that **plugins do not notice**. A caching or security plugin will carry on writing its block into `.htaccess`, report success, and have no effect whatsoever. Nothing errors.

## The setting you must change at the same time

This one is not obvious and it is the single most expensive mistake available here.

If **Serve static files directly by nginx** is switched on while Proxy mode is off, Plesk stops applying the site's custom nginx directives. Plesk documents it in an article whose title is the whole warning: *"Additional nginx directives are not applied when nginx proxy mode is disabled in Plesk"*. The stated cause is that *"the option 'Serve static files directly by nginx' interferes with additional nginx directives"*, and the stated fix is to disable that option.

That matters because the custom directives box is where every rule you have just translated out of `.htaccess` would live. Leave the setting on, and your permalink rule, your deny rules and your security rules are all quietly absent. Plesk's article records only that the directives are not applied, and nothing in the panel warns you.

**So: turn Serve static files directly by nginx off when you turn Proxy mode off.** It is redundant in this mode anyway. There is no Apache to bypass, so nginx serves your static files either way.

## Permalinks: the three lines that matter

This is the payoff. WordPress on nginx is ordinary, and the whole permalink problem is one directive.

WordPress publishes this as its own single-site rule:

```nginx
location / {
    try_files $uri $uri/ /index.php?$args;
}
```

Read it left to right: is this a file on disk? Is it a directory? Neither? Then hand it to WordPress, with the query string intact.

**Use the `$is_args` form instead.** WordPress's REST API documentation warns about a different variant, `/index.php$args` with no separator at all, and gives this as the replacement:

```nginx
location / {
    try_files $uri $uri/ /index.php$is_args$args;
}
```

That is not a correction of the rule above it. Both forms carry the query string, and both fix permalinks. The one WordPress warns about is `/index.php$args`, missing the `?` entirely, and its documented symptom is that query parameters such as `?page=2` or `?_embed` are "not having any effect" on the REST API. Adding `$is_args`, "which will print a `?` character if query arguments are found", is what WordPress says "will allow WordPress to properly receive and interpret the query parameters". Prefer `$is_args$args` anyway, for a smaller reason: `$is_args` is `?` when the request has a query string and empty when it does not, so it never appends a bare `?` to a request that had no parameters, and `REQUEST_URI` stays clean.

Verified on a live site with Apache out of the path: with that block and nothing else, the home page, permalinks, `/wp-json/`, feeds, sitemaps, `wp-login.php` and `wp-admin` all return `200`.

You may not need to add it at all. Plesk generates a permalink fallback itself for sites it recognises as WordPress, and states that new WordPress installations have worked under nginx-served PHP since Plesk Obsidian 18.0.34. Test before you assume either way, with a real request rather than an admin screen: WordPress does not warn about missing rewrite rules when it detects nginx, so a site with broken permalinks looks perfectly healthy in **Settings > Permalinks** while every URL except the home page returns 404.

**On Noiz shared plans the custom nginx directives field is managed by Noiz.** Send the rules you need on a ticket and they will be applied and tested. On a VPS or dedicated server where you hold the panel's administrator login, the field is on the same **Apache & nginx Settings** page.

## Translating the rest of your .htaccess

Every common rule has a native equivalent, and several are better in nginx because they are compiled configuration rather than a file re-read on every request.

| Your `.htaccess` does | nginx does |
|---|---|
| WordPress permalink block | `try_files $uri $uri/ /index.php$is_args$args;` |
| `Redirect` or `RewriteRule ... [R=301]` | `return 301 https://yourdomain.com/new-path;` |
| `deny from all`, `Require all denied` | `location = /wp-config.php { deny all; }` |
| `ExpiresByType` | `expires 30d;`, scoped to a static-file location rather than the whole site. The **Expires** field on the same panel page does not apply here: it configures Apache's `mod_expires`, and Apache is out of the path |
| `Options -Indexes` | nothing to do, `autoindex off` is nginx's default |
| Hotlink protection on `HTTP_REFERER` | `valid_referers none blocked server_names;` with `$invalid_referer` |
| Password-protected directory | `auth_basic` with the same `.htpasswd` file |
| `ErrorDocument 404 /404.php` | `error_page 404 /404.php;` |
| Force HTTPS | a server-level `return 301`, or Plesk's own SEO-safe redirect setting |
| `php_value` / `php_flag` | nothing to do, nginx never reads them. They were not harmless before the change either: with PHP-FPM and no `mod_php` loaded, an unguarded `php_value` line makes Apache return 500 for that directory. Per-site PHP values belong on the PHP Settings page |

Five things that catch people out when translating:

- **Order decides which rule wins, and a wrong order still passes the configuration test.** nginx checks regular-expression locations "in the order of their appearance in the configuration file" and "the search of regular expressions terminates on the first match". A regular-expression deny such as `location ~* /wp-config\.php$ { deny all; }` must therefore appear *before* the block that handles `.php` files, or the PHP block matches first and the file is executed instead of denied. Ordering does not matter for the exact-match form in the table above, because an `=` match terminates the search immediately, which is why it is the safer one to write.
- **Be careful what you put `^~` on.** Exact matches (`location = /xmlrpc.php`) and prefix matches (`location ^~ /.well-known/`) are more predictable than regular expressions and worth preferring. But nginx's rule is that "if the longest matching prefix location has the `^~` modifier then regular expressions are not checked", so a `^~` block that wins the prefix match switches off every regular-expression location for those URLs. Never put one on `/wp-content/uploads/` on a WooCommerce store. It would suppress the `woocommerce_uploads` rule below and serve paid files to anyone who guesses the URL, and the configuration test would still pass.
- **`return 301` sends the URL literally.** Apache's `Redirect` appends whatever path remains after the matched prefix and carries the query string through; nginx's `return` does neither. Use `return 301 https://yourdomain.com/new-path$is_args$args;` to keep the query string, and `$request_uri` where the whole remaining path must be preserved. Translating a prefix redirect without this collapses every URL under it onto one page.
- **Query strings append by default.** In Apache you add `[QSA]` to carry the query string across a rewrite. In nginx that is the default behaviour, so it is the *absence* of `[QSA]` that needs work, not its presence. Translating this backwards produces duplicated parameters on faceted pages.
- **Hotlink protection is not a security control on either server.** nginx says so itself: fabricating a `Referer` header "is quite easy", and the module exists "to block the mass flow of requests sent by regular browsers", not to block them thoroughly. Regular browsers also legitimately omit the header.
- **Expiry headers behave slightly differently.** Apache's `mod_expires` stands down when the application already set the header. nginx's `expires` does not: it is documented as "adding or modifying" those fields. Scope it to a static-file location rather than applying it site-wide, or a cart page can end up advertising itself as cacheable.

## WooCommerce: do this before you sell a downloadable product

WooCommerce protects paid downloads by writing an `.htaccess` file containing `deny from all` into `wp-content/uploads/woocommerce_uploads/`. Under the Force downloads and X-Accel-Redirect methods, **that file is the only thing stopping a direct request for a paid file**. On nginx it does nothing.

WooCommerce documents the requirement itself:

> If you use an NGINX server with the X-Accel-Redirect/X-Sendfile or Force downloads method, you need to modify your server configuration for proper file protection.

The replacement is one directive:

```nginx
location ~* /wp-content/uploads/woocommerce_uploads/ {
    internal;
}
```

`internal` means the location "can only be used for internal requests. For external requests, the client error 404 (Not Found) is returned", while still permitting the internal redirect WooCommerce uses to deliver a legitimate purchase. Do not apply it to a store using the **Redirect only** download method, which needs those files publicly reachable.

Once it is in place, `X-Accel-Redirect` becomes available, which is a better delivery method than Force downloads: the file is handed to nginx to stream instead of being pushed through a PHP process that stays occupied for the whole download.

## Who should not use this

Say no to this tier if any of the following is true. These are disqualifications, not things to work around.

- **A security plugin manages your `.htaccess`.** Its protections stop applying and its admin screen will not tell you. Some plugins do detect nginx and hide the options, which is better, but the protection is still gone.
- **A caching plugin serves pages through `.htaccess` rewrites.** Some keep working through PHP instead and merely become slower; others stop entirely. Establish which before, not after.
- **You edit `.htaccess` yourself.** If self-service rewrite and deny rules are part of how you run the site, this tier removes that and replaces it with a support request. That is a fair trade for some people and an unacceptable one for others.
- **You rely on SSI, Perl or Python.** Plesk lists all three as unavailable in nginx-only mode.

## The case for it, honestly stated

**Speed.** One web server instead of two, and no Apache worker held for the length of every response. Apache's own documentation notes that for proxied and FastCGI content "the MPM can't predict the end of the response and a worker thread has to finish its work before returning the control to the listener". Removing that hop removes that constraint.

**A cost you pay even with an empty `.htaccess`.** Apache's documentation is unusually blunt here:

> Thus, permitting .htaccess files causes a performance hit, whether or not you actually even use them!

The reason is that Apache looks for the file in every directory along the path to the requested file, on every request, whether or not any of them exist. A site with no `.htaccess` at all is paying for the search.

**A smaller attack surface.** In the twelve months to August 2026, Apache published two privilege-escalation issues in the same class, both described in Apache's own words as allowing "local `.htaccess` authors to read files with the privileges of the httpd user": CVE-2026-24072, fixed in httpd 2.4.67 on 4 May 2026, and CVE-2026-44119, fixed in httpd 2.4.68 on 8 June 2026. Apache rates both moderate; the CVSS v3.1 base scores recorded at NVD are 8.8 for CVE-2026-24072 and 5.5 for CVE-2026-44119. Ten separate researchers were credited on the second, which is a fair sign the class is being actively looked at.

Noiz applies these updates, so this is not a statement that your site is exposed today. The point is structural: WordPress requires `.htaccess` to be writable, so a compromised plugin inherits the ability to write one, and that capability keeps producing this class of issue. Under nginx-only, the file is inert.

There is also a version of the problem with no patch, and it is documented behaviour rather than a bug. `AllowOverride FileInfo` is what makes WordPress permalinks work. The same override group also grants `SetHandler`, `Action` and the `mod_mime` directives, so anyone who can write an `.htaccess` file can by design change how a file is executed. Apache's own reference lists them together.

## Proving the change worked

Configuration review cannot catch the failure modes here. A wrongly-ordered location passes the configuration test. Discarded directives pass it too. **Only a live request proves anything.** Run all of these against the site after the change:

```bash
D=yourdomain.com

curl -s -o /dev/null -w "home        %{http_code}\n" "https://$D/"
curl -s -o /dev/null -w "permalink   %{http_code}\n" "https://$D/a-real-post-url/"
curl -s -o /dev/null -w "rest api    %{http_code}\n" "https://$D/wp-json/"
curl -s -o /dev/null -w "login       %{http_code}\n" "https://$D/wp-login.php"
curl -s -o /dev/null -w "htaccess    %{http_code}\n" "https://$D/.htaccess"
curl -s -o /dev/null -w "wp-config   %{http_code}\n" "https://$D/wp-config.php"
```

The first four must be `200`. `/.htaccess` must be `403` or `404`: a `200` there means the file is being served as plain text, which publishes whatever is in it.

`/wp-config.php` is the one to read carefully, because the status code on its own proves nothing. With no Apache in the path, PHP-FPM executes `wp-config.php` like any other PHP file, so unless you have added a deny rule it returns `200` with an empty body. That is the correct result, not a failure. What matters is the body:

```bash
curl -sf "https://$D/wp-config.php" | head -c 40
```

That must print nothing. The `-f` suppresses the body of a `403` or `404`, so silence covers all three safe outcomes: denied, not there, or executed and empty. Anything printed needs explaining, and anything containing `<?php` or `DB_PASSWORD` means the file is being served as source rather than executed. If you would rather see a `403` on that URL than an empty `200`, add the `wp-config.php` deny rule from the translation table above, and put it before the block that handles `.php` files.

Two more worth running. Check that a query string survives:

```bash
curl -s "https://$D/wp-json/wp/v2/posts?per_page=1" | head -c 80
```

And check the media library still accepts a real upload from **Media > Add New**, since the upload size limit is enforced by nginx in this mode.

If the site sells downloadable products, request a known product file directly by URL. It must return `404`.

## Rolling back

**Take the nginx directives out first.** With Proxy mode on, `location /` belongs to Plesk's own generated configuration, so leaving your own `location /` block in the directives field makes nginx refuse the configuration with `duplicate location "/"` and the reload fails. Remove the rules you added for nginx-only mode before you change anything else. They are redundant in any case once Apache is back in the path, because `.htaccess` is doing that work again. On a Noiz shared plan this is part of the same ticket.

Then select **Proxy mode** again. After that, check the PHP handler on the **PHP Settings** page and set it back to an Apache-served one if it is still nginx-served, because PHP being executed by nginx is what stops `.htaccess` running, and that is what you are rolling back for.

With the directives cleared first, the change is a configuration regeneration and a reload. It takes seconds, and it does not touch your files or your database.

## Troubleshooting

**Symptom**: only the home page loads, everything else is a 404. The permalink rule is missing. Add the `try_files` block, and do not trust **Settings > Permalinks**, which shows no warning on nginx.

**Symptom**: `?page=2` or `?_embed` has no effect on the REST API. Look for a `try_files` line ending in `/index.php$args`, with no `?` and no `$is_args`. That is the broken form WordPress documents, and the query string is being glued onto the filename. Replace it with `/index.php$is_args$args`. A line ending in `/index.php?$args` is not this fault, so if that is what you have, the cause is elsewhere.

**Symptom**: a redirect that worked yesterday does not. It was in `.htaccess`. Translate it to a `return 301` or hand it to a redirect plugin that works in PHP rather than by writing files.

**Symptom**: a security plugin reports everything is fine but its rules are clearly not applying. Expected. Its rules were `.htaccess` rules. Fold what you need into the nginx configuration.

**Symptom**: a paid download is reachable without buying it. The WooCommerce `internal` block is missing. Add it before anything else.

**Symptom**: WordPress generates `http://` links on an `https://` page. PHP is not being told the request arrived over TLS. Raise it on a ticket; it is a one-line server-side fix, not a WordPress setting.

## Need a hand?

Noiz can review a site against this list and tell you plainly whether it is a candidate, including what is in its `.htaccess` and which plugins would be affected. Open a ticket with the domain name and ask for an nginx-only assessment.

---
Source: https://docs.noiz.ie/plesk/running-wordpress-on-nginx-without-apache/
