When I got talked into starting a blog, I decided I’d at least have some fun with it and install the blog software myself — after all, I’m already running a web server; why not use that? Should be easy, right?
Well, turns out it was easy, except one “little snag.” I could see the blog template, change settings, post and comment — but I couldn’t upload media. Some digging turned up that WordPress stuffs everything besides media into its underlying MySQL database, but media files are directly uploaded to the directory structure managed by the web server running the WordPress software. Whenever I tried uploading media, I got the dreaded “Unable to create directory – Is its parent directory writable by the server?” error message.
Now, being an old Linux hand, I blamed permission issues right away, and started doing the usual tests — find out under which user account apache and php scripts are run, chown the offending directory tree to that user, set proper permissions on that directory tree, all to no avail. I even tried the last-resort fix of making the entire wordpress directory world-writable (don’t try this at home), but even that didn’t work. And that had me stumped.
So I turned to Google, and found out that about 10 million other people had already run into the same problem. There were lots of proposed fixes, most of them bogus of course (use ftp to upload media; manually create a new uploads/<year>/<month> directory every month; make everything world-writable; disable your plug-ins; re-install WordPress; change hosting services; exorcise your server; do a rain dance, etc.). Not a single one of them worked, including the rain dance.
Now here was the actual issue: my web server is sitting on a modern Linux kernel, and if you know your stuff you know where I’m going with this: it’s running SELinux. And of course SELinux was the culprit. It turns out that apache runs under a very restrictive SELinux regime (as it should), and this regime makes all files underneath the root html directory read-only, no matter what the UNIX file permissions say. You can chmod 0777 to your heart’s content, it will make not an ounce of difference. But once you re-label WordPress’ upload directory to httpd_sys_rw_content_t, everything is peachy keen.
So why is this so hard? SELinux is not to blame; it’s doing exactly what it’s supposed to be doing. It’s running apache in the most restrictive way possible that allows it to run at all. Changing the label of a directory is a one-line command, and there’s even a man page (httpd_selinux) that explains what needs to be done. So why on earth did not a single one of the answers I found mention SELinux at all? (Of course, in hindsight, if you Google for “WordPress SELinux media upload” you get lots of good advice, but if you are already suspecting SELinux is involved, you don’t need to Google anymore).
I think the basic problem is that the fact that SELinux has completely changed the way UNIX does security has stayed under the radar for most people. UNIX file permissions are no longer the first line of defense against unauthorized file accesses. I myself completely forgot about SELinux while frantically trying to solve the problem; I only remembered it when I had already wasted a good part of a day. What I’m saying is, SELinux needs a really good PR campaign. Oh, and somebody needs to write a blog post containing the keywords “WordPress cannot upload images” and “SELinux” and “httpd_sys_rw_content_t” so that poor noobs like me will be able to find it on Google in the future. Oh, I guess I just did.
So here’s my solution, where /var/www/html/wordpress is my wordpress root directory, and apache is running as user apache:
- Chown the entire /var/www/html hierarchy to root:apache and make it readable for user and group only:
- chown -R root:apache /var/www/html
- chmod -R g-w,o-rwx /var/www/html
- Create (if it doesn’t already exist) and chown /var/www/html/wordpress/wp-content/uploads to apache:apache and make sure it’s user-writable (it should already be):
- mkdir /var/www/html/wordpress/wp-content/uploads
- chown -R apache:apache /var/www/html/wordpress/wp-content/uploads
- chmod -R u+w,g-w,o-rwx /var/www/html/wordpress/wp-content/uploads
- Change the SELinux context of /var/www/hml/wordpress/wp-content/uploads to httpd_sys_rw_content_t:
- chcon -R –type=httpd_sys_rw_content_t /var/www/html/wordpress/wp-content/uploads
- Optionally, make the change permanent, i.e., make it survive a complete file system re-label:
- semanage fcontext -a -t httpd_sys_rw_content_t “/var/www/html/wordpress/wp-content/uploads(/.*)?”
Oh, and if you think my advice to make all of /var/www/html verboten for other is too restrictive, at least do the world a favor and make the WordPress configuration file, you know the one that contains your MySQL database’s admin password in plain text, yeah go ahead and make that one user- and group-only.
Ah hell, while you’re at it, just save yourself a lot of trouble and go ahead and read this.