Tutorials

Bootstraping WordPress/Nginx/PHP/MySQL on a Cheap VPS with lowendscript

One constantly posted question on LowEndBox and LowEndTalk is — how to get the same setup as LowEndBox, a WordPress site that runs on cheap VPS with 80MB of memory? Instead of doing a step-by-step explanation (damn I hate writing long blog posts), I decided to wrap it into a bash script that does it all.

NginxPHP
WordPressMySQL

It is based on my previous script here, and it has been tested on both Debian 5 “Lenny” and Ubuntu 10.04 “Lucid Lynx”. Sorry no other distributions are supported. It is currently hosted at github so feel free to branch it and tune it to your heart’s desire.

Usage

Here is a quick step by step on how to set up a WordPress blog running on Nginx, PHP/FastCGI and MySQL on a low end box. Let’s assume that you are going to set up a blog on hostname “blog.example.com“.

  1. Go and buy a cheap VPS. Yes, you know where to find cheap virtual servers under $7/month, don’t you? 🙂 I am using my 6 pound/year RackVM VPS with 128MB memory to test this script, and the performance turns out to be quite good. YMMV.

    Make sure it is either a Debian or a Ubuntu distro though.

  2. After gaining root access to your VPS,
    # wget --no-check-certificate https://github.com/lowendbox/lowendscript/raw/master/setup-debian.sh
    ...
    # bash setup-debian.sh system
    ...
    # bash setup-debian.sh exim4
    ...
    # bash setup-debian.sh nginx
    ...
    # bash setup-debian.sh mysql
    ...
    # bash setup-debian.sh php
    ...
    # bash setup-debian.sh wordpress blog.example.com
    ...
    
  3. Now altering your DNS entry to point blog.example.com to your new VPS (or alter your local /etc/hosts entry). Navigate to it from your browser, and you should be presented with WordPress set up page. Fill in your blog’s name, admin’s password and contact email, and your new WordPress is online!

    Just repeat the last step (running bash setup-debian.sh wordpress <hostname>) to set up more blogs on the same box.

That’s it!

Software Stack

It’s pretty similar to my own stack that runs LowEndBox.com.

  • dropbear to replace openssh. Invoked from xinetd.
  • inetutils-syslogd to replace rsyslog.
  • exim4 to replace sendmail (if installed). Re-configured to allow Internet delivery.
  • cron
  • nginx
  • mysql. Re-configured to remove innodb support, remove query cache and reduce key buffer size.
  • php with built-in FastCGI interface. Running only 1 child process and respawn after 5,000 requests.

The lowendscript is also

  • Removing some commonly bundled applications that should not be there in the first place for a minimal distro (apache2, sendmail, bind9, samba, nscd, etc).
  • MySQL root is given a new password (which can be found in ~root/.my.cnf)
  • Installing each WordPress site under /var/www/<hostname>. It will create appropriate database, users and password for the site.

I have to say that it is still “work in progress”, but if you want to figure out the internals please check the source code of that bash script.

Memory Usage

I am testing the same script on the following 3 systems:

  • Ubuntu 10.04 64bit (RackVM)
  • Debian 5 64bit (RackVM)
  • Debian 5 32bit (VPSGuy)

They are all showing different memory usage. I am taking the snapshot of memory usage AFTER 10 page loads on the WordPress blog that has been created to ensure PHP process is fully loaded.

Ubuntu 10.04 64bit

root@ubuntu64:~# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.5  23300  1536 ?        Ss   06:58   0:00 init
root     10124  0.0  0.5  23576  1496 ?        Ss   07:34   0:00 dropbear -i
root     15742  0.0  0.7  17880  1944 pts/0    Ss   07:35   0:00 -bash
root     15754  0.0  0.4  14960  1084 pts/0    R+   07:35   0:00 ps aux
root     24290  0.0  0.3  12508   788 ?        S    07:22   0:00 /usr/sbin/syslo
root     24524  0.0  0.3  21064  1004 ?        Ss   07:22   0:00 cron
root     25689  0.0  0.3  19512   972 ?        Ss   07:22   0:00 /usr/sbin/xinet
mysql    26346  0.0  4.2  61812 11248 ?        Ssl  07:23   0:00 /usr/sbin/mysql
www-data 27920  0.0  2.6  87768  7048 ?        Ss   07:23   0:00 /usr/bin/php-cg
www-data 27922  0.5 15.0 122460 39432 ?        S    07:23   0:04 /usr/bin/php-cg
103      30162  0.0  0.3  44356  1000 ?        Ss   07:24   0:00 /usr/sbin/exim4
root     30267  0.0  0.3  28104   932 ?        Ss   07:24   0:00 nginx: master p
www-data 30268  0.0  0.8  28744  2196 ?        S    07:24   0:00 nginx: worker p

root@ubuntu64:~# free
             total       used       free     shared    buffers     cached
Mem:        262144     100500     161644          0          0          0
-/+ buffers/cache:     100500     161644
Swap:            0          0          0

Memory Usage: 98.1MB

Debian 5 64bit

debian64:~# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2  10304   740 ?        Ss   12:22   0:00 init [2]
106       1414  0.0  0.3  42708  1004 ?        Ss   12:26   0:00 /usr/sbin/exim4
www-data  1655  0.0  2.5  72028  6560 ?        Ss   12:26   0:00 /usr/bin/php-cg
www-data  1657  0.9 11.7  98500 30928 ?        S    12:26   0:03 /usr/bin/php-cg
www-data  3126  0.0  0.7  28380  1912 ?        S    12:29   0:00 nginx: worker p
root      7668  0.1  0.5  23340  1528 ?        Rs   12:31   0:00 dropbear -i
root      7831  0.0  0.6  17472  1716 pts/0    Ss   12:32   0:00 -bash
root      7836  0.0  0.3  14720   980 pts/0    R+   12:32   0:00 ps aux
root     20169  0.0  0.3  18540   936 ?        Ss   12:22   0:00 /usr/sbin/cron
root     24462  0.0  0.2  12236   732 ?        S    12:25   0:00 /usr/sbin/syslo
root     25637  0.0  0.3  19256   952 ?        Ss   12:25   0:00 /usr/sbin/xinet
root     25694  0.0  0.5  28380  1552 ?        Ss   12:25   0:00 nginx: master p
root     32427  0.0  0.2   3872   588 ?        S    12:26   0:00 /bin/sh /usr/bi
mysql    32466  0.0  3.0  50420  8124 ?        Sl   12:26   0:00 /usr/sbin/mysql
root     32468  0.0  0.2   3772   592 ?        S    12:26   0:00 logger -p daemo

debian64:~# free
             total       used       free     shared    buffers     cached
Mem:        262144      61008     201136          0          0          0
-/+ buffers/cache:      61008     201136
Swap:            0          0          0

Memory Usage: 59.6MB

Debian 5 32bit

debian32:~# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1   1980   688 ?        Ss   23:34   0:00 init [2]
root     14064  0.0  0.1   1704   512 ?        S    23:52   0:00 /bin/sh /usr/bi
mysql    14119  0.0  1.7  20056  6968 ?        Sl   23:52   0:00 /usr/sbin/mysql
root     14120  0.0  0.1   1628   536 ?        S    23:52   0:00 logger -p daemo
www-data 15968  0.0  1.2  16352  4996 ?        Ss   23:53   0:00 /usr/bin/php-cg
www-data 15971  0.5  4.7  29436 18576 ?        S    23:53   0:01 /usr/bin/php-cg
root     16040  0.0  0.3   2700  1464 ?        Rs   23:53   0:00 dropbear -i
root     16119  0.0  0.1   1880   684 ?        S    23:44   0:00 /usr/sbin/syslo
root     16133  0.0  0.3   2780  1536 pts/0    Ss   23:54   0:00 -bash
root     17728  0.0  0.2   2352   896 ?        Ss   23:45   0:00 /usr/sbin/xinet
www-data 17758  0.0  0.3   5004  1488 ?        S    23:54   0:00 nginx: worker p
root     27834  0.0  0.3   4548  1236 ?        Ss   23:47   0:00 nginx: master p
root     28146  0.0  0.2   2296   896 pts/0    R+   23:57   0:00 ps aux
102      32065  0.0  0.2   6104   900 ?        Ss   23:49   0:00 /usr/sbin/exim4

debian32:~# free
             total       used       free     shared    buffers     cached
Mem:        262144      44272     217872          0          0          0
-/+ buffers/cache:      44272     217872
Swap:            0          0          0

Memory Usage: 43.2MB

So to put that in summary form

  • Ubuntu 10.04 uses significantly more memory than Debian 5 (PHP5.3/MySQL5.1 vs. PHP5.2/MySQL5.0). So pick your distro wisely and maybe you don’t need the latest and greatest if your application does not need it.
  • 32bit also uses quite a bit less than 64bit, running exactly the same version of stack. That pretty much confirms what I posted before.

And hopefully the above script is useful for your next WordPress splog farm 😛

490 Comments

  1. eM's avatar
    eM

    great one, thanks for sharing. now, who’s up to convert it to centos? 🙂

    • Ryan's avatar
      Ryan

      Why would you want to do that?

  2. Bryan's avatar
    Bryan

    Very cool! 🙂

    I can see what I can do to convert it to CentOS.

  3. Qwizie's avatar
    Qwizie

    I get the following when I tested it for my main blog. Is their anyway to tune this? I have little knowledge of nginx

    502 Bad Gateway

    nginx/0.6.32

  4. Jaygiri's avatar
    Jaygiri

    Another great post with much more info then the old post.

    again when are you providing such a nice tutorial on postfix install and full configuration for virtual domains

    Many Thanks.

  5. LEA's avatar
    LowEndAdmin Author

    @Qwizie — 502 Bad Gateway means Nginx cannot talk to PHP fastcgi.

    Make sure your PHP are running. I have installed the init script at /etc/init.d/php-cgi, and just do invoke-rc.d php-cgi start to start it.

  6. Qwizie's avatar
    Qwizie

    It did work until I change A record for my major traffic site to the IP and now that’s all I get.

    vps:~# invoke-rc.d php-cgi start
    Starting php-cgi: php-cgi/usr/bin/php-cgi already running.
    invoke-rc.d: initscript php-cgi, action “start” failed.

    Is their a way to tune PHP to handle more PHP CGI requests?

  7. LEA's avatar
    LowEndAdmin Author

    Will then I guess you need to “restart” rather than “start” then. You can put

    PHP_FCGI_CHILDREN=3

    /etc/default/php-cgi

    or whatever number of children processes you want there. But remember they all use up memory 🙂

  8. bekanosky's avatar
    bekanosky

    @LEA :”respawn after 5,000 requests”
    Can you give explanation to this?

    Thanks

  9. LEA's avatar
    LowEndAdmin Author

    Just some random number that works for me. Restarting the child process before too much memory leak (if any). Don’t you also reboot your Windoze box every 7 days? 🙂

    • Josh's avatar
      Josh

      Nope, my Windows machine has been running for like 2 months straight without a hitch.

      • Ryan's avatar
        Ryan

        Lies… Everyone knows Microsoft products are incapable of running more than 24 hours without falling over.

        • Erawan Arif Nugroho's avatar
          Erawan Arif Nugroho

          Why are you commenting on a 2011 post?
          And about Microsoft Products, maybe it depends on the provider. I have 512MB Windows 2008 VPS which is running for 2 months without problem. The only restart will be happened if I turn on the automatic updates,

  10. yohan's avatar
    yohan

    Installed it at debian 5 32-bit(spacerich canis) and it’s wordpress 3 for only 50MB total usage! my initial try of normal wp3 installation on lighttpd eats more than 384MB (from a how-to-forge tutorial)… I have to downgrade back to 2.9.2… I’ll stick with this setup for now. Thank you! 😀

  11. Qwizie's avatar
    Qwizie

    heh

    PHP_FCGI_CHILDREN=75
    and my.cnf is skip-innodb and 100 conn and to cache.

    top – 08:35:27 up 3:24, 3 users, load average: 26.27, 15.94, 7.08
    Tasks: 52 total, 22 running, 22 sleeping, 8 stopped, 0 zombie
    Cpu(s): 93.1%us, 6.9%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
    Mem: 524288k total, 521748k used, 2540k free, 0k buffers
    Swap: 0k total, 0k used, 0k free, 0k cached

    It loads at times, and others not. I wanted to see if I could put a high traffic site on a lowendbox using this setup.

  12. bekanosky's avatar
    bekanosky

    @LEA.
    OK, thanks for the clarification. Actually i set the number to 50 request. And yes, just a random number :D.
    Hmm.. i don’t use windows , just use ubuntu 😀

  13. LEA's avatar
    LowEndAdmin Author

    @Qwizie – 75 is most definitely too many for probably any size of sites on a single box. Usually I will only use 1-2 times the number of CPU cores (unless your PHP processes spend too much time on IO wait, which is a problem in its own), and for a simple WordPress site like LowEndBox.com, PHP_FCGI_CHILDREN=1 is more than enough. Too much causes to use too much memory. And in your case there is no swap so the process probably just get killed.

    @bekanosky – 50 is probably fine since PHP starts up pretty fast.

  14. Qwizie's avatar
    Qwizie

    @LEB

    I’m receiving around 150-200 HTTP connections a second. So I increased it to the amount I had around in my lighttpd config on my dedicated. But I was still stocked to see that it somewhat worked.

  15. LEA's avatar
    LowEndAdmin Author

    150-200 HTTP connections per second does not translate to 150-200 PHP requests per second, does it? Your OpenVZ VPS is running next to no memory and with 75 FastCGI PHP processes, even at 10MB each (even WordPress 3 brings them to ~30MB) will get you to 750MB — so your VPS is probably suffering from OOM at the moment.

    My other site with ~75 HTTP req/second uses about 6 PHP processes, just to give a point of reference.

  16. Josh's avatar
    Josh

    LEA,
    how to check http req/second and php request/second?

  17. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    I get error at step 4, please let me know how to solve 😀

    bash setup-debian.sh nginx

    root@endor:~# bash setup-debian.sh nginx
    Reading package lists…
    Building dependency tree…
    Reading state information…
    Package nginx is not available, but is referred to by another package.
    This may mean that the package is missing, has been obsoleted, or
    is only available from another source
    E: Package nginx has no installation candidate
    nginx installed for nginx
    setup-debian.sh: line 147: /etc/nginx/conf.d/lowendbox.conf: No such file or directory
    invoke-rc.d: unknown initscript, /etc/init.d/nginx not found.

  18. LEA's avatar
    LowEndAdmin Author

    @Minh — Nginx is part of the universe repository on Ubuntu, so make sure you have universe on your apt/source.list. For example,

    $ cat /etc/apt/sources.list
    deb http://us.archive.ubuntu.com/ubuntu/ lucid main restricted universe
    deb http://security.ubuntu.com/ubuntu lucid-updates main restricted universe
    deb http://security.ubuntu.com/ubuntu lucid-security main restricted universe
    

    Maybe I will need to check that in the script…

  19. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    @bekanosky: Thanks, but i got another error from first step 😀
    E: Type ‘main’ is not known on line 1 in source list /etc/apt/sources.list
    E: Type ‘main’ is not known on line 1 in source list /etc/apt/sources.list
    E: The list of sources could not be read.

  20. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    @LowEndAdmin: Great, it’s working fine 😀 Many thanks!!!

    When i upgrade plugin on WordPress, it required FTP info, what should i do now? Should i type SSH info?

  21. bekanosky's avatar
    bekanosky

    @minhmeo
    ah, you should ask asim for this issue :D.
    But, i guess you missed ‘#’ in first line when doing copy-paste

  22. LEA's avatar
    LowEndAdmin Author

    @Minh — no one should use FTP for upgrading WordPress. Well this script is only getting the sites going and as a demo on how to script something to bootstrap a site running on a low end box. Although for production sites I’ll put more thoughts on how to maintain it in the long run.

    Also you might want to look at using subversion to maintain your WordPress installations, including plugins.

  23. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    Thanks! i will upgrade manually 😀

    After doing all steps, i run “free -m” to see ram usage and it’s 115mb, is it normal? 😀

    I do “reboot” and my site get error: 502 Bad Gateway

    I see that you told “Qwizie — 502 Bad Gateway means Nginx cannot talk to PHP fastcgi.”
    and i run command “invoke-rc.d php-cgi start” or “invoke-rc.d php-cgi restart” but system say: -bash: invoke-rc.d: command not found

  24. Unknow's avatar
    Unknow

    How about replacing MySQL by SQLite database in wordpress? it use less memory.
    You can also replace exim4 by ‘ssmtp’ (Extremely simple MTA to get mail off the system to a Mailhub).

  25. Tom's avatar
    Tom

    This look great, I’ll try it out when rackvm goes back online.

  26. LEA's avatar
    LowEndAdmin Author

    @Minh — might need to use update-rc.d php-cgi defaults to add php-cgi to RC. Please dig Debian manual on that although I should have added that into the script. And are you sure you are using Debian/Ubuntu based system — sysv-rc package should have been installed. Also you need to be running as root 🙂

    @Unknown — SQLite is not supported by stock WordPress, and as I am always grabbing the latest release there is no guarantee that 3rd party plugins will work. There are other excellent blogging engines such as Habari that uses SQLite. I am only using WordPress because LowEndBox uses it, and it seems to be a quite popular choice.

    As of SSMTP yes I have used that in my 32MB VPS example last year. But it’s error prone, no queue and little guarantee of delivery — unless you are running a stable relay somewhere else. I much prefer to have a box that is self-contained.

  27. Jackk's avatar
    Jackk

    Does any body know how I get nginx to display directory listings?

  28. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    @LowEndAdmin: Yes, i’m running as root. I still get error

    root@endor:~# update-rc.d php-cgi defaults
    -bash: update-rc.d: command not found

  29. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    @bekanosky:

    root@endor:~# /etc/init.d/php-cgi start
    * Starting php-cgi php-cgi start-stop-daemon: Unable to open pidfile ‘/var/run/www/php.pid’ for writing: No such file or directory (No such file or directory)

  30. LEA's avatar
    LowEndAdmin Author

    Looks like you did not run `bash setup-debian.sh php` properly? It should have

    1. Created `/var/run/www` directory
    2. Change owner to www-data:www-data
    3. Install various FastCGI and init scripts

    I am pretty sure it has been tested on Ubuntu 10.04. It actually calls `update-rc.d php-cgi defaults` at the end of installing PHP so PHP/FastCGI will start after reboot.

    Something wrong on your end.

    • SinnerSaint's avatar
      SinnerSaint

      The same thing happens to me. I already reinstalled my VPS three times with the same OS (Ubuntu 10.04 32-Bit) and followed the instructions you provided. The script was run without any errors encountered and I was able to set up a blog. The only problem is whenever the server is restarted, php-cgi and mysql does not automatically run.

      The reason why this is happening in Ubuntu only is because whenever you restart the server, the `/var/run/www` directory is deleted automatically. I tried this script in Debian 5 and there are no problems there.

      My only workaround is to manually run the following commands manually after each server reboot:

      invoke-rc.d mysql start
      mkdir -p /var/run/www
      chown www-data:www-data /var/run/www
      invoke-rc.d php-cgi start

      Do you have any way to make this automated or at least make the functionality working? I tried editing the files /etc/nginx/fastcgi_php and /etc/init.d/php-cgi to change the directory of the php.sock and php.pid but it’s not helping either.

      • joe's avatar
        joe

        I still have the same issue. Any workaround/fix for this?

        • dgandhi's avatar
          dgandhi

          I had the same issue, the problem is that ubuntu uses a RAM-disk for /var/run, so changes to the dir structure are not preserved through a restart.

          If you dig through the /etc/init/mounted-* config files you can probably figure a way to make changes permanent, but I just modified the init script to move the socket and pid files to a directory on /var/www which I know is on a disk partition.

        • Jason's avatar
          Jason

          I have fixed this problem by editing the script and changing the location of where the pid file is stored prior to running the script. Instead I am installing it to /var/lib/www.

          Apparently /var/run is not a good spot to put things. Source: http://ubuntuforums.org/showthread.php?t=996892

          I have actually created a fork of the original script on Github to add functionality for Drupal installations and fixed this bug along the way.

          https://github.com/sk33lz/lowendscript

  31. Jackk's avatar
    Jackk

    LowEndAdmin – Any idea how I get nginx to output directory listings for directory’s with no index file. I currently get a 403 error.

    Jack.

    • nn's avatar
      nn

      I have the same error

      403 Forbidden

      nginx/0.7.67

  32. Ellimist's avatar
    Ellimist

    @Jackk:
    Put autoindex on; inside the location directive for that directory.
    It isn’t, however, that simple to make it work. You have to move the root statement out of the location. (More info)

  33. Minh@Minh Meo's Blog's avatar
    Minh@Minh Meo's Blog

    @LowEndAdmin: I did exactly as your tutorial and it’s working fine, wordpress blog run fine, but ram usage so high (>500mb ram) so i “reboot” and i get error “502 Bad Gateway”

Leave a Reply

Before you comment
  • Please use your hosting provider's support system for service issues.
  • Comments may be held temporarily for spam review.
  • Use <pre>...</pre> for terminal output, or link to a paste service.

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