VPS, Ubuntu, WordPress og permissions

WordPress på en VPS kan være et godt alternativ til delt hosting på et webhotel, men det kan være besværligt at få filejerskab og filrettigheder på helt på plads, så WordPress fungerer som man er vant til. Mange af de trin-for-trin guides til opsætning af WordPress på en VPS efterlader brugeren med en konfiguration hvor det ikke er muligt at installere plugins fra kontrolpanelet uden at bruge FTP. Denne artikel giver et bud på hvordan problemet kan løses på Ubuntu 14.04 LTS.

Priserne på VPSer (virtuelle private servere) er efterhånden så langt nede at de kan være et godt alternativ til delt hosting på et almindeligt webhotel, specielt hvis man ikke kan nøjes med helt low-end hosting. For eksempel koster en 1GB Linode $10 om måneden hvilket med dagens valutakurser svarer til lige knap 60 kroner, og andre VPS-udbydere har lignende priser. Fordelen ved at bruge en VPS er at den kan skræddersys til at passe præcis til et bestemt websites behov, hvor man på delt hosting bruger en konfiguration der er beregnet på et "gennemsnitligt" website. En VPS er også lettere at skalere hvis man f.eks. får flere besøgende og derfor får brug for at bruge mere serverkacitet.

Opsætning af en VPS kræver et vist kendskab til Linux, men mindre end man umiddelbart skulle tro. Installation og konfiguration af en VPS til f.eks. at køre WordPress er ikke mere kompliceret end at man uden særlige forkundskaber kan få det til at virke i løbet af en dags tid, hvis man blot har et minimum af interesse. Linode og DigitalOcean har gode trin-for-trin vejledninger man kan følge, og ellers giver en simpel Google-søgning masser af hits.

Et problem med disse trin-for-trin vejledninger er imidlertid at man ofte ender med en WordPress installation som ikke fungerer helt som man er vant til fra shared hosting. Et typisk problem man render ind i er at WordPress beder om FTP-oplysninger når man forsøger at installere et plugin på den nye VPS:

FTP Credentials

I andre tilfælde kan man opleve at WordPress fungerer fint, men at man ikke kan uploade filer til WordPress med FTP eller SFTP uden efterfølgende at skulle bruge kommandoer som chmod og chown for at gøre filerne tilgængelige i WordPress. Alle disse problemer har med filrettigheder at gøre, og her kan det være svært at finde brugbar information via en Google-søgning. Man finder mange kreative forslag til hvordan disse filrettigheder skal sættes, og guldkornene er desværre begravet mellem et væld af irrelevante forslag. Det er imidlertid muligt at lave en opsætning der undgår de ovenstående problemer.

Opsætning

Vores udgangspunkt er en VPS der kører Ubuntu 14.04.1 LTS. Apache, MySQL og PHP er installeret fra Ubuntus repositories med apt-get. Vi har oprettet en Apache virtual host for domænet h2g2.dk med følgende indhold:


  ServerName eksempel.dk
  ServerAlias www.eksempel.dk
  ServerAdmin info@stickleback.dk
  DocumentRoot /var/www/eksempel.dk/public_html
  
      AllowOverride all
  
  ErrorLog ${APACHE_LOG_DIR}/eksempel.dk/error.log
  CustomLog ${APACHE_LOG_DIR}/eksempel.dk/access.log combined

Apache suEXEC

WordPress skal installeres i "DocumentRoot" mappen /var/www/eksempel.dk/public_html som allerede er oprettet med kommandoen

$ sudo mkdir -p /var/www/eksempel.dk/public_html

Mapperne /var/www/eksempel.dk og /var/www/eksempel.dk/public_html ejes af brugeren root, og med Ubuntus standardindstillinger kan Apache (og systemets øvrige brugere) kun læse filer i disse mapper, ikke skrive til dem. Det første vi gør er derfor at skifte ejerskabet af disse mapper.

På shared hosting ser man ofte at alle webhotellets filer og mapper er ejet af brugeren selv. Output fra ls -l kan f.eks. se således ud:

drwxr-xr-x 12 lisa lisa   4096 10 sep 07:31 ./
drwxr-xr-x  6 lisa lisa   4096  5 okt 18:33 ../
-rw-r--r--  1 lisa lisa  24165 10 sep 07:31 admin-bar.php
-rw-r--r--  1 lisa lisa  10918 15 apr  2014 atomlib.php
-rw-r--r--  1 lisa lisa  15166 10 sep 07:31 author-template.php
-rw-r--r--  1 lisa lisa  13425 10 sep 07:31 bookmark.php
-rw-r--r--  1 lisa lisa  11497 10 sep 07:31 bookmark-template.php
-rw-r--r--  1 lisa lisa  19065 10 sep 07:31 cache.php
-rw-r--r--  1 lisa lisa  25062 10 sep 07:31 canonical.php
-rw-r--r--  1 lisa lisa  39066 10 sep 07:31 capabilities.php
-rw-r--r--  1 lisa lisa  11185 10 sep 07:31 category.php
-rw-r--r--  1 lisa lisa  44452 10 sep 07:31 category-template.php

I dette tilfælde er alle filer og mapper ejet af brugeren lisa. Mønsteret -rw-r--r-- på filerne angiver at brugeren selv har adgang til at læse filen og skrive til den, medlemmer af brugergruppen lisa kan læse filen (men ikke skrive til den), og alle andre brugere på systemet kan ligeledes kun læse filen. Mapper har mønsteret drwxr-xr-x hvilket tilsvarende betyder at det kun er lisa der har adgang til at oprette nye filer i disse mapper. (De to mønstre svarer til hhv 644 og 755 hvis man foretrækker at se rettighederne udtrykt som tal).

På denne delte server kører Apache som brugeren nobody. Denne bruger har kun læseadgang til lisas filer og mapper, men WordPress kan alligevel installere plugins mm uden problemer. Forklaringen er at Apache kører i suEXEC mode - hver gang der kommer en HTTP-forespørgsel til indhold på brugeren lisas website starter Apache en ny proces der kører som brugeren lisa, og denne proces har dermed adgang til at oprette nye filer og skrive til eller slette eksisterende filer. Når HTTP-forespørgslen er slut og der ikke længere er brug for den nye Apache proces nedlægges den igen.

Brugen af suEXEC har nogle sikkerhedsmæssige fordele på shared hosting, og der er ingen problemer forbundet med upload af filer via FTP eller SFTP fordi disse automatisk vil være ejet af lisa brugeren. Vi kunne vælge at bruge suEXEC på vores VPS, men det koster serverresourcer som er et unødvendigt overhead på en VPS der kun er beregnet på at køre ét website. (Specielt hvis man også bruger SuPHP til at køre PHP scripts med ejerens brugernavn).

Rettigheder uden suEXEC

Med standardopsætningen af Ubuntu 14.04 kører Apache som brugeren www-data, og denne bruger er medlem af gruppen www-data. Vi vil gerne have at lisa fortsat ejer alle filer og mapper på eksempel.dk websitet for at gøre det så simpelt som muligt for ejeren af websitet at uploade filer via FTP / SFTP. Hvis vi dernæst giver medlemmer af gruppen www-data de nødvendige rettigheder til at oprette og skrive til filer, så vil WordPress fungere på samme måde som på shared hosting. Eksemplet ovenfor kommer så til at se således ud:

drwxrwxr-x 12 lisa www-data   4096 10 sep 07:31 ./
drwxrwxr-x  6 lisa www-data   4096  5 okt 18:33 ../
-rw-rw-r--  1 lisa www-data  24165 10 sep 07:31 admin-bar.php
-rw-rw-r--  1 lisa www-data  10918 15 apr  2014 atomlib.php
-rw-rw-r--  1 lisa www-data  15166 10 sep 07:31 author-template.php
-rw-rw-r--  1 lisa www-data  13425 10 sep 07:31 bookmark.php
-rw-rw-r--  1 lisa www-data  11497 10 sep 07:31 bookmark-template.php
-rw-rw-r--  1 lisa www-data  19065 10 sep 07:31 cache.php
-rw-rw-r--  1 lisa www-data  25062 10 sep 07:31 canonical.php
-rw-rw-r--  1 lisa www-data  39066 10 sep 07:31 capabilities.php
-rw-rw-r--  1 lisa www-data  11185 10 sep 07:31 category.php
-rw-rw-r--  1 lisa www-data  44452 10 sep 07:31 category-template.php

Hvis man sammenligner de to eksempler kan man se at der er kommet et ekstra w på gruppetilladelserne, og gruppen er skiftet fra lisa til www-data.

Vi kan bruge disse Linux-kommandoer til at skifte rettigheder og ejerskab til alle filer og mapper under /var/www/eksempel.dk:

$ cd /var/www
$ find eksempel.dk -type d | xargs sudo chown lisa:www-data
$ find eksempel.dk -type d | xargs chmod 775
$ find eksempel.dk -type f | xargs sudo chown lisa:www-data
$ find eksempel.dk -type f | xargs chmod 664

De to første kommandoer tager sig af mapperne, de to sidste af filerne.

Nu skulle man så tro at alt var godt, men vi er ikke helt færdige. Vi mangler at sørge for at nye filer oprettes med den korrekte gruppe, og vi skal overbevise WordPress om at det godt må installere plugins mm. selv om Apache kører som en anden bruger end ejeren af filerne.

Nye filer

Det ejerskab og de rettigheder vi etablerede ovenfor giver Apache adgang til eksisterende filer og mapper, men nye filer og mapper som lisa opretter får ikke det korrekte ejerskab. F.eks. giver kommandoen

$ touch test.fil

Følgende resultat:

drwxrwxr-x 12 lisa www-data   4096 10 sep 07:31 ./
drwxrwxr-x  6 lisa www-data   4096  5 okt 18:33 ../
-rw-rw-r--  1 lisa www-data  24165 10 sep 07:31 admin-bar.php
-rw-rw-r--  1 lisa www-data  10918 15 apr  2014 atomlib.php
-rw-rw-r--  1 lisa www-data  15166 10 sep 07:31 author-template.php
-rw-rw-r--  1 lisa www-data  13425 10 sep 07:31 bookmark.php
-rw-rw-r--  1 lisa www-data  11497 10 sep 07:31 bookmark-template.php
-rw-rw-r--  1 lisa www-data  19065 10 sep 07:31 cache.php
-rw-rw-r--  1 lisa www-data  25062 10 sep 07:31 canonical.php
-rw-rw-r--  1 lisa www-data  39066 10 sep 07:31 capabilities.php
-rw-rw-r--  1 lisa www-data  11185 10 sep 07:31 category.php
-rw-rw-r--  1 lisa www-data  44452 10 sep 07:31 category-template.php
-rw-rw-r--  1 lisa lisa      43412 15 okt 17:31 test.fil

Læg mærke til at filen i den nederste linje ovenfor ikke giver nogen rettigheder til www-data.

Mapper i Linux har en group ID bit som vi kan bruge til at angive at alle nye filer som oprettes i mappen skal have samme gruppe-ejerskab som mappen selv har. Vi kan sætte denne bit på mappen /var/www/eksempel.dk med følgende kommando:

$ sudo chmod g+s eksempel.dk

Inden denne kommando så mappen eksempel.dk således ud:

drwxrwxr-x  3 lisa  www-data 4096 Oct 15 17:25 eksempel.dk/

Efter at have kørt ovenstående kommando:

drwxrwsr-x  3 lisa  www-data 4096 Oct 15 17:25 eksempel.dk/

Gruppe-rettighederne der før var rwx er nu rws. Hvis vi nu opretter en ny fil i eksempel.dk ser det således ud:

$ touch test.fil
$ ls -l test.fil
$ -rw-rw-r-- 1 lisa www-data 0 Oct 15 17:33 test.fil

Nye mapper under eksempel.dk "arver" group ID bit'en automatisk, men den skal sættes manuelt på evt. eksisterende mapper. Det kan gøres med følgende kommando:

$ cd /var/www
$ find eksempel.dk -type d | xargs sudo chmod g+s

FS_METHOD

Når Linux fil-ejerskab og rettigheder er på plads ville man forvente at WordPress kunne installere plugins mm. uden at beklage sig. Hvis man prøver bliver man imidlertid igen promptet for FTP-login oplysninger.

Forklaringen findes i følgende stump kode fra WordPress core-filen wp-admin/includes/file.php:

function get_filesystem_method($args = array(), $context = false) {
    $method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets'

    if ( ! $method && function_exists('getmyuid') && function_exists('fileowner') ) {
        if ( !$context )
            $context = WP_CONTENT_DIR;

        // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it.
        if ( WP_LANG_DIR == $context && ! is_dir( $context ) )
            $context = dirname( $context );

        $context = trailingslashit($context);
        $temp_file_name = $context . 'temp-write-test-' . time();
        $temp_handle = @fopen($temp_file_name, 'w');
        if ( $temp_handle ) {
            if ( getmyuid() == @fileowner($temp_file_name) )
                $method = 'direct';
            @fclose($temp_handle);
            @unlink($temp_file_name);
        }
    }
...

Det som sker her er at WordPress forsøger at finde ud af om det er muligt at skive direkte til filsystemet via PHP. WordPress opretter først en midlertidig fil med navnet temp-write-test- og et tidsstempel. Dernæst åbnes den samme fil og WP tjekker at resultatet af getmyuid() er identisk med ejeren af den midlertidige fil.

Ejeren af den midlertidige fil er www-data, og da koden i file.php afvikles af Apache skulle man umiddelbart tro at kaldet til getmyuid() også ville returnere www-data. Det er imidlertid ikke tilfældet. getmyuid() returnerer navnet på ejeren af file.php, ikke ejeren af Apache-processen. Og da file.php er ejet af brugeren lisa fejler sammenligningen og WP konkluderer at det ikke har de nødvendige rettigheder og falder tilbage på FTP.

Som med så meget andet er løsninger enkel når først problemets årsag er fundet. Indsæt følgende i wp-config.php:

define('FS_METHOD','direct');

Med denne indstilling springer WordPress tjekket i file.php over og herefter dukker skærmbilledet med FTP-oplysningerne ikke længere op når man forsøger at installere plugins.

(WordPress-dokumentation for FS_METHOD skriver at det at sætte den eksplicit til "direct" is fraught with opening up security issues on poorly configured hosts. This is chosen automatically when appropriate.". Jeg er ikke klar over hvilke security issues der hentydes til her, men det er både acceptabelt og nødvendigt at bruge denne indstilling i dette tilfælde).