The answer to: How do you set directories to 755 and files to 644?

AKA: Choosing and setting safe file permissions for a WordPress install.

 

If you have ever watched a WordPress security presentation, you’ve heard the advice:

Generally speaking, directories should be 755 and files should be 644.

But the presenter never tells you how to change these settings for all the directories and files in the WordPress install. I’m guilty of the same thing in my presentation.  After all, Bash doesn’t go over well in front of general audiences.

But how do you change all the directories and files to have the settings that you want? You’re certainly not going to go around and manually change all of the permissions file by file & directory by directory!  And if you did, would you still be able to upload files and upgrade your plugins with those permissions? Probably not.

I thought I would share how I handle this on my servers.  My method is not the most refined, and does have some minor issues, but it works and gets me 95% to where I should be.

I have this function built into my .bashrc file:

function defaultr()
{
find . -type d | xargs chmod -v 755
find . -type f | xargs chmod -v 644
find . -type d -iname uploads | xargs chmod -Rv 774
find . -type d -iname blogs.dir | xargs chmod -Rv 774
find . -type d -iname cache | xargs chmod -Rv 774
find . -type d -iname themes | xargs chmod -Rv 774
find . -type d -iname plugins | xargs chmod -Rv 774
find . -type d -iname upgrade | xargs chmod -Rv 774
}

It is meant to be run from a www or public_html directory with WordPress installed under it.  This will follow the basic rule, and modify the special directories so that you can upload, upgrade and use a caching tool.  Additionally, it works for WordPress with multi-site either enabled or disabled.
Now, if you wanted to have some real fun, you could add lock and unlock functions so that there can be no changes to your plugins and themes unless you want them to be changed and then your site IS secure.

function lock()
{
find . -type f -iname wp-config.php | xargs chmod -Rv 644
find . -type d -iname themes | xargs chmod -Rv 754
find . -type d -iname plugins | xargs chmod -Rv 754
find . -type d -iname upgrade | xargs chmod -Rv 754
}

function unlock()
{
find . -type f -iname wp-config.php | xargs chmod -Rv 774
find . -type d -iname themes | xargs chmod -Rv 774
find . -type d -iname plugins | xargs chmod -Rv 774
find . -type d -iname upgrade | xargs chmod -Rv 774
}

Of course that means you need to be confident and not panic when you see messages like “Cannot update/modify/create x” and remember to unlock things before making those changes, but largely, you can set and forget these things until you are ready to apply updates.

One final note. The numbers in these examples are suitable for many shared hosts. If you pay more than $10/month for hosting, chances are you will be able to changes those numbers to better suit your needs. When I lock the files down, I use 544 and sometimes lower.

In any case, this is a good baseline for your functions and will get the job done.  If you have a similar/better technique, I’d love to hear it.

(BTW have you figured out where the flaws are?)

Addendums:

  • I should mention that the settings here are verbose.  You can change the -Rv to -Rcf or just -Rf and get the same results less space taken up.
  • If you get an error like “chmod: missing operand after ‘744’” This is “normal”. It just means that your install does not have one of the directories. Maybe you’ve never upgraded a plugin and the upgrade directory doesn’t exist. Or you have never run multisite and blogs.dir does’t exist.  You can either remove these lines or create empty directories, if the errors bother you.
  • I’ve added echo lines so I can determine which commands create errors

In Linux, how do you unzip all the zip files in a directory?

This is a simple issue that trips me up every now and then.  Let’s say you have a directory of a couple dozen themes all in zip files.  It would be a pain to type “unzip filename.zip” for every single one, but when you do an “unzip *.zip” you get something like:

[root@hosting ~]# unzip *.zip
Archive: file1.zip
caution: filename not matched: file2.zip
caution: filename not matched: file3.zip
caution: filename not matched: file4.zip

You see the problem is that as command line runs, the * is processed redundantly via both unzip and the *nix CLI. In the end, it tries to extract from the first file it finds, all of the remaining files in the directory by name.

The trick to fix this is simple. Escape the * with a back slash when making the call. Then the * is passed as a character to unzip and nothing more.  Like so:

[root@hosting ~]# unzip \*.zip
Archive: file1.zip

inflating: …

Escaping symbols and aliases is important in other circumstances as well.  Every now and then a Linux command simply won’t work because the CLI has replaced text mid command with other text.  I once couldn’t FTP in somewhere because the password was replaced before FTP could actually run. Escaping the symbols in the password involved would have allowed the connection to work..

Adding a line to /etc/hosts via Perl

Having appended a line to /etc/hosts through BASH, what I really wanted to do, was add it via PERL.

The basic operation would be the same: load a text file into a single variable, search a string for text, and if not found append text to a file in Perl.

So, here is the conversion of that earlier bash script:

#!/usr/bin/perl
# Configure environment to promote good programming practices
use strict;
use warnings;

# Define initial variables & constants
use constant NOT_FOUND => -1;        # All good programmers use Constants
my $domain="example.com";            # Change it to meet your needs
my $needle="www.$domain";            # I search with www. prefix
my $hostline="127.0.0.1 $domain www.$domain";
my $filename="/etc/hosts";           # Standard host file location

# Read the file into a scalar var in the most backwards compatible way
local( $/, *FILEHANDLE );
open(FILEHANDLE, $filename) or die("Cannot access $filename");
my $haystack = ;
close(FILEHANDLE);

# Look for the domain in the file already
my $result = index($haystack, $needle);

# Index returns 
if ( $result == NOT_FOUND )
{
  print "$needle NOT found in $filename\n";
  # If the line wasn't found, add it using an echo append >>
  open(FILEHANDLE, ">>" . $filename);
  print FILEHANDLE $hostline . "\n"; #write newline
  close(FILEHANDLE);
  print "$hostline added to $filename\n";
} 
else
{
  print "$needle already exists in $filename\n";
}

As a refresher on PERL, implied lessons in this are:
To concatenate strings in PERL you can either embed them within the double quoted string or concat them with the period like PHP does.
Lines end in semicolons of course
If causes change from brackets and fi in BASH to parents and braces in PERL
Constants are done with “use constant”
vars are usually defined with a my like basics var – there are of course variations on this.
Print vs echo
comparisons get the double = as in php

Adding a line to /etc/hosts via bash

It is easy to append a line to a file in BASH.  I wrote a short script this morning that I can use to add a single line to my servers /etc/hosts pointing any domain I want at the loopback IP address of 127.0.01.

So I thought I would share..

#!/bin/bash
SUCCESS=0                      # All good programmers use Constants
domain=example.com             # Change this to meet your needs
needle=www.$domain             # Fortunately padding & comments are ignored
hostline="127.0.0.1 $domain www.$domain"
filename=/etc/hosts

# Determine if the line already exists in /etc/hosts
echo "Calling Grep"
grep -q "$needle" "$filename"  # -q is for quiet. Shhh...

# Grep's return error code can then be checked. No error=success
if [ $? -eq $SUCCESS ]
then
  echo "$needle found in $filename"
else
  echo "$needle not found in $filename"
  # If the line wasn't found, add it using an echo append >>
  echo "$hostline" >> "$filename"
  echo "$hostline added to $filename"
fi