User-based Website Directories with Nginx
Many webservers allow users on the machine to create personal websites, usually with urls starting with /~username
, where the web files are contained in a directory like /home/username/public_html/
. With Apache and Lighttpd, this is done with the module mod_userdir
, but there does not appear to be an equivalent for Nginx. I developed a method for doing so, using only Nginx’s standard configuration directives.
One website recommended using Nginx’s rewrite rules to emulate user-based website directories. This worked fairly well, except for one problem. Nginx has this internal behavior of fixing URL requests for directories so that they have a trailing slash. But when a rewrite rule was applied first, the fixed URL, presented to the web browser, was the rewritten location (with the trailing slash).
The rewrite rule looked like:
/~[username]/[file] => /home/[username]/[file]Then I would make a request for:
http://www.sbf5.com/~cduan/subdirand Nginx would rewrite the URL, add the slash, and redirect my browser to:
http://www.sbf5.com/home/cduan/subdir/which, of course, caused an error.
My solution to this problem was to first parse the URL to determine the username, requested file, and presence or absence of trailing slashes. Then, I manually redirect requests for directories that lack trailing slashes, thus circumventing any attempt by Nginx to (mis)redirect the requests on its own.
I developed one “trick” to get around Nginx’s lack of support for nested conditionals. Whenever I came to a point where I would normally have used a nested conditional, I used a rewrite to add a special prefix to the front of the URL, and then created a special location
block, using that special prefix, where execution would continue (and thus where I could put a second conditional). Nginx always puts a slash in front of URLs it creates, so all of my special prefixes contained no leading slash, ensuring that a user could not accidentally stumble on them.
The code is as follows:
Improvements and suggestions are greatly appreciated.# For requests starting with a tilde, break them into three components: # 1. The username, everything after the tilde up to the first slash # 2. The file location, everything after the username up to the last slash # 3. The trailing slash(es) # Then, rewrite to go to the f~/ branch. location /~ { if ($request_uri ~ ^/~([^/]*)(/.*[^/]|)(/*)$) { set $homedir $1; set $filedir $2; set $trailingslashes $3; rewrite ^/~([^/]*)(/|$)(.*)$ f~/$3; } }Here, the user-directory components have been parsed. Use an alias to set
the file directory prefix. But if the file at the requested URI is a
directory, we jump to the ~/ branch for additional processing.
location f~/ { alias /home/$homedir/public_html/; if (-d /home/$homedir/public_html$filedir) { rewrite ^f~/(.*) ~/$1; } }
Here, the request is for a directory in a user’s home directory. We check
that the request URI contained trailing slashes. If it did not, then we
add the slashes and send a redirect. This circumvents Nginx’s faulty
internal slash-adding mechanism.
location ~/ { autoindex on; alias /home/$homedir/public_html/; if ($trailingslashes = “") { rewrite .* /~$homedir$filedir/ redirect; } }
UPDATE: If you want PHP support, follow the instructions in this post.