Clean URLs (Permalinks) for WordPress on Lighttpd

I’ve moved my blog in the last few days to a new, bigger dedicated server (as well as some other sites I own). After doing some benchmarks (I plan to post those soon), I’ve decided to switch to Lighttpd. While the exact migration notes are the topic of another post, I can say that I’m fairly satisfied with the move.

After setting up the server, I started moving the blog. Importing the files and the database was pretty straightforward. But when I thought everything was ready and I transferred the domain to the new server, I’ve found out that none of my inner pages are accessible. The reason, as it turned up pretty quickly, is that WordPress depends on Apache’s mod_rewrite to create the clean URLs (the so-called permalinks). This actually posed two problems:

  1. WordPress depends on Apache’s mod_rewrite.
  2. WordPress uses .htaccess files for the clean URLs configuration.

Luckily, Lighttpd has its own mod_rewrite; however, it operates a bit differently. The WordPress-generated .htaccess has the following lines:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

These rules tell Apache to check if the requested URL is a file or directory that exists on the server; otherwise, append the requested URL to index.php. The last statement can be written in Lighttpd’s mod_rewrite syntax as

"^/(.+)/?$" => "/index.php/$1"

If we only use this rule, the posts will load up fine, but all static files will not be available; that includes CSS files and images, which isn’t desired. Apache handles this by checking if the requested URL exists as a file or directory. Unfortunately, Lighttpd’s mod_rewrite has no way to check if a file exists. I’ve found out that a possible workaround for this is to use mod_magnet, which allows embedding Lua code in Lighttpd, and use it to check if files exist and handle the clean URLs. This seemed to me like overkill, and as some people noted, it has some performance hit, which I wanted to avoid.

I’ve figured out that I shouldn’t just translate the rewrite rules; instead, I need to adapt them. In Apache, we check if a file exists in order to be able to serve the static files. The directories are checked so the server would be able to list them. The problem is now to achieve those objectives in Lighttpd. First, we don’t really need to allow the directories to be listed (actually I’ve planned to block this option in my blog a long time ago). So the rule about the directories can be fully ignored. All the static files that need to be served have an extension; this includes all the files I’ve uploaded. We note that WordPress-generated permalinks never have a dot (‘.’) in their URLs. So we can exploit this behavior to achieve our goal. The following condition uses regular expressions to check if a given URL has an extension, and if so, serves it.

"^/(.*).(.+)$" => "$0"

The only difference from the Apache configuration is that the web server itself returns the 404 message when a requested file is missing, instead of passing it to WordPress and letting it return the error.

To sum everything up, our mod_rewrite configuration should look like this:

url.rewrite = (
    "^/(.*).(.+)$" => "$0",
    "^/(.+)/?$" => "/index.php/$1"
)

As I noted before (the second problem), there is no equivalent to .htaccess files in Lighttpd. So this configuration has to go directly into the main configuration file in /etc/lighttpd/lighttpd.conf (or wherever it exists on your server). Make sure you’ve enabled mod_rewrite, as this is required. You can enable it by putting the following line somewhere above the url.rewrite block.

server.modules += ("mod_rewrite")

If you have multiple sites hosted, you should include the url.rewrite block inside a conditional statement. For example, my configuration looks like this:

$HTTP["host"] =~ "(www.)?guyrutenberg.com" {
    url.rewrite = (
        "^/(.*).(.+)$" => "$0",
        "^/(.+)/?$" => "/index.php/$1"
    )

    [...other unrelated configurations...]
}

And I’ve enabled mod_rewrite at the top of the /etc/lighttpd/lighttpd.conf file.

This workaround works well, as you can read this post now. If you run into problems, or have any questions about why or how I did something, don’t hesitate to comment.

62 thoughts on “Clean URLs (Permalinks) for WordPress on Lighttpd”

  1. Setup a webserver on an old android phone this morning. Put up a WP site for the heck of it. This tutorial is amazing, thanks!

  2. Hi, thank you for your guide.

    My WordPress + Lighttpd setup stopped working with the server.error-handler-404 = “/index.php” solution and gives me a 404 error unless I’m logged in to WordPress.
    So I tried to use your settings to solve the issue but it didn’t help. Any advice would be really appreciated.

  3. OK, this worked and solved the problem with permalinks, but when I have this code in my lighttpd.conf, I cannot get to my wordpress admin area to show – the URL in the browser shows “www.bernardotech.org/wp-admin, but the page in the browser is simply the home page. Any thoughts on what might be causing this? If I remove you rewrite code, I regain access to the admin area, but then other stuff is broken.

  4. OK, I actually got it working. I got this line from another site and added it within the rule that you posted:

    Exclude some directories from rewriting

    "^/(wp-admin|wp-includes|wp-content|gallery2)/(.*)" => "$0",

    Now everything is working, as least as near as I can tell.

  5. thank you Eldon. I had the same problems like you , wordpress wp-admin link not working and another software in the same server not working.
    this article is incomplete. lucky with the comments.

  6. For anyone on Lighttpd with Gutenberg crashing on error TypeError: "e.visibility is undefined" (in the browser console).

    The line causing the Gutenberg crash in my case was:
    "^/(wp-.+).*/?" => "$0"

    I changed the above to "^/wp-admin/$" => "$0" and Gutenberg no longer crashes. So my virtual host config now looks like:

    $HTTP["host"] =~ "your-virtual-host-name" {
        server.document-root = "your/doc/root/here"
    
        url.rewrite-if-not-file = (
            "^/wp-admin/$" => "$0",
            "^/.*?(\?.*)?$" => "/index.php$1"
        )
    
        server.error-handler-404 = "/index.php"
    }
    
    

Leave a Reply

Your email address will not be published. Required fields are marked *