Linode vs AWS pricing

Recently, i moved my linode to new plans and compare it with latest EC2 pricing. I have myself 2 “1024” and 1 “2048” server in linode which just cost USD45 per month. Personally which is a very good deal. I served my clients on AWS as AWS can do much more things and actually cheaper if you buy bigger servers.

It is actually not easy to find a better price between them if you have no data on your application. In my application, bottleneck is on Memory instead of CPU. I believe most application are same with me, that is why Linode pricing plan base on Memory as well.


If your server shows CPU always 10-20% and you are running websites / PHP things. This price comparison may be useful to you.


Screen Shot 2014-12-17 at 2.11.21 PM Screen Shot 2014-12-17 at 2.10.38 PM

Comparing Linode 8192 plan and EC2 m3.large, the monthly price is AWS more expensive. But, dont forget Linode require USD20 extra for the backup plan, so it will be same price as AWS. Someone may say AWS lot fewer computation power, i agree with it. My application is memory driven, so i don’t care.

In my case, AWS is cheaper when the machine is more powerful. Like if we just need 1024 / 2048 memory, USD 10/20, you can buy nothing in AWS, but you can have really good servers at linode.

Extra features

While price don’t change, AWS also offer VPC, cross region AMI copying, IAM user control, load balancer pay by traffic, nearly free S3 storage which you will be expensive to have all these in Linode.


One of the problem of Linode is their servers will sold out. So, for enterprise usage, like ElasticMapReduce / Auto-scaling, you cannot do it on Linode. Remember, High availability is cost. AWS reserved many instance in each data center, so that you can pay for it when you need it. Linode cannot offer like if I need 10 more servers anytime I want.


In Summary, i would suggest people to use Linode if you are just considering to have < 4096 memory per server. You dont need architecture like separate servers into NAT zones.

If you are serving big server architecture, you are better to go for AWS due to rich features and availability. And for the big servers, AWS actually 50%+ cheaper when you go for the pre-paid reserved instances.

Why bootdev — Caching

Cache structure

For an LNMP structure, you probably will have 3 layers of caching to protect your servers from hitting too much.

Page cache: MicroCache / Varnish / Cloudfront Dynamic Content

PHP Cache: APC / OpsCache (PHP 5.5+)

in memory cache: ElastiCache (memcache) / ElastiCache (Redis)

In BootDev, we choose MicroCache + APC + memcache. It is a traditional approach here. We have REVOLUTION plan, that use mostly Cloudfront Dynamic Content service, but that is still experimental. Let’s put that later.

Page cache

Cache invalidation sucks. To advoid this, we use microcache buildin nginx rather than using Varnish. With Varnish, of coz you can speed up per every single request. But, you need to invalidate cache when content update. But with Microcache, what we do is expire the content every 40s, so that when the SECOND+ user hit the same page, he will spend up. We only render pages at the first request, so that we use less memory, but same performance if your site traffic is enough.

If you want to make sure every request HIT cache, we suggest to add cache-warmer, which is build in @bootdev management machine (CHEF Server) too.

Here are some basic config Reference:

fastcgi_cache_path /dev/shm/microcache levels=1:2 keys_zone=MYAPP:5M max_size=256M inactive=2h;
fastcgi_cache_key “$scheme$request_method$host$request_uri”;
add_header X-Cache $upstream_cache_status;
map $http_cookie $cache_uid {
default nil; # hommage to Lisp 🙂
~SESS[[:alnum:]]+=(?<session_id>[[:alnum:]]+) $session_id;
map $request_method $no_cache {
default 1;
GET 0;

#Cache everything by default
set $no_cache 0;
#Don’t cache POST requests
if ($request_method = POST)
set $no_cache 1;
#Don’t cache if the URL contains a query string
if ($query_string != “”)
{    set $no_cache 1;
#Don’t cache the following URLs
if ($request_uri ~* “/(administrator/|login.php)”)
set $no_cache 1;
#Don’t cache if there is a cookie called PHPSESSID
if ($http_cookie = “PHPSESSID”)
set $no_cache 1;

location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
#NOTE: You should have “cgi.fix_pathinfo = 0;” in php.ini
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_pass unix:/var/run/php-fpm-www.sock;
fastcgi_read_timeout 40;
fastcgi_cache MYAPP;
fastcgi_cache_valid 200 301 30s;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;

# Set cache key to include identifying components
fastcgi_cache_valid 302     1m;
fastcgi_cache_valid 404     1s;
fastcgi_cache_min_uses 1;
fastcgi_cache_use_stale error timeout invalid_header updating http_500;
fastcgi_ignore_headers Cache-Control Expires;
fastcgi_pass_header Set-Cookie;
fastcgi_pass_header Cookie;

## Add a cache miss/hit status header.
add_header X-Micro-Cache $upstream_cache_status;

## To avoid any interaction with the cache control headers we expire
## everything on this location immediately.
expires epoch;

## Cache locking mechanism for protecting the backend of too many
## simultaneous requests.
fastcgi_cache_lock on;

The first few lines of code is to tell where to store cache, and the limit. Also, try to handle auth user. (It need details tunning per site) But, in general , above lines of code will be enough.

Remember to have    #NOTE: You should have “cgi.fix_pathinfo = 0;” in php.ini

PHP Cache

For code cache, we use APC. And later will switch to OpsCache, as APC for PHP 5.4- OpsCache for PHP 5.5+

Share some of the php-fpm.d/www.conf . It really depends on your server resource, calculation and loading test result.

listen = /var/run/php-fpm-www.sock #using .sock over port 9001 will be faster, if your php server and nginx are on the same server, it will be ok.

pm = dynamic

pm.max_children = 50
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 10

request_terminate_timeout = 40

In php.ini we have

memory_limit = 256M

Config above is base on loading test result that we enabled APC and also run under EC2 m3.large with 2000 users from blazemeter.

In memory cache

I see some people will now use Redis over memcache. For bootdev, we use memcache as it is fast and simple enough.

For redis, you can have more advance features like query it with noSQL. But, we don’t need to use all these as in memory cache for Drupal is just use to cache block / views / other things from cache-api. You should install php-memcache library too.

Add below to Drupal with Drupal memcache module, so that you can have memcache backend.

$conf[‘cache_backends’][] = ‘sites/all/modules/contrib/memcache/’;
$conf[‘cache_default_class’] = ‘MemCacheDrupal’;
$conf[‘cache_class_cache_form’] = ‘DrupalDatabaseCache’;
$conf[‘memcache_key_prefix’] = ‘your_prefix’;
$conf[‘memcache_servers’] = array(
‘yourCacheNode:11211’ => ‘default’,
‘yourCacheNode:11211’ => ‘others’,

$conf[‘memcache_bins’] = array(
‘cache’ => ‘default’,
‘cache_block’ => ‘others’,
‘cache_content’ => ‘others’,
‘cache_filter’ => ‘others’,
‘cache_form’ => ‘others’,
‘cache_menu’ => ‘others’,
‘cache_page’ => ‘others’,
‘cache_update’ => ‘others’,
‘cache_views’ => ‘others’,
‘cache_views_data’ => ‘others’,

You can set different type of cache to different cache node. Of coz, you need to prepare cache node in AWS first. And that is why bootdev, one click for both server architect and relative configurations 🙂

Now you have better understanding of caching in bootdev or (Drupal + AWS)

Why bootdev — CDN configuration

CDN illustration

Why bootdev CDN config

One of BootDev’s website backend feature is CDN auto-deploy. The idea is we deploy the right way of using Amazon Cloudfront (CDN) automatically. You don’t need to investigate and do lots of work in Drupal to make things done.

From our hard earned experience, Drupal CDN configuration summarize as 2

  1. File storage + CDN (Drupal AmazonS3 module + CNAME with Cloudfront)
  2. Directly CDN (Drupal CDN Module)

1. Drupal AmazonS3 module

This module is great. It helps to move Drupal file system on Cloud. The advantage is you don’t need big local storage. But, you will be headache of using this. Some examples are

  • Generating XMLsitemap, the cron job will hang as it is not using local storage
  • new image style, you will need to turn off AmazonS3 module to add new style
  • Features module operations take longer, especially with image field operations
  • Multi-upload / insert image to editor, as the style generation link will be shown at the first time (/system/styles/files/….) will be different when the same style shown second time (CDN url). If you are not notice about it, your image will keep generate and waste lots lots of CPU.
  • Nginx Support, you will need to add AmazonS3 Nginx support (try url /system/stles/files)

For the multi-upload problem, you will need to insert images when u “edit” it rather than create. But, it will complain by your content editor. Another solution is running a quick fix database script to change the url in text body after image generated.

For the others, share some code snap as below, you can put this in your Drupal settings.php or config in admin page. Of coz, you will need to set your AWS right way like setting right S3 bucket as the Cloudfront origin, map to Route53 CNAME of your domain and put it on Cloudfront CNAME. That’s why you need bootdev if you are not understand what I’m talking about.

$conf[‘aws_key’] = ‘your key’;
$conf[‘aws_secret’] = ‘your secret’;
$conf[‘amazons3_bucket’] = ‘your bucket for AmazonS3 module’;
$conf[‘amazons3_cache’] = ‘1’;
$conf[‘amazons3_cloudfront’] = ‘1’;
$conf[‘amazons3_cname’] = ‘1’;
$conf[‘amazons3_domains’] = ‘
your mutiple CNAME, to increase frontend performance.
You will need this patch ‘;

For support nginx, so that image styles can be generated, you will need this

  # Catch image styles for D7.
location ~ ^/sites/.*/files/ {
try_files $uri @rewrite;

# Catch image styles for AmazonS3 D7.
location ~ ^/system/files/styles/ {
try_files $uri @rewrite;

2. Drupal CDN module

OK, now images are served. But what about the css/js/other static resources ? You will need CDN module.

You will need to config AWS to pull your data which we are not going to explain here howto. After that, you can have your CDN module to alter your site url, so that for examples: .css .js .xx will alter to CDN url.

Here is some basic config which u can put into your Drupal settings.php

$conf[‘cdn_basic_mapping’] = ‘| .css .js|.ttf .woff .svg .eot’;
$conf[‘cdn_mode’] = ‘basic’;

//CDN conf (Improve frontend performance)
$conf[‘cdn_farfuture_status’] = ‘1’; is your CDN url, you can have other CNAME. You will see i put as a CDN, which is not a good way. Why we are doing this is, we want to keep the webfront files serve as local. So that it won’t have CORS problem. Otherwise your font won’t work. Another approach is uploading your font to S3 manually and then manually set S3 CORS header and then set it as ClountFront origin. But, it make things complex and the font will leave the git repo management, which harder to deploy. So, we suggest to serve it locally, if you do not a very very large site.

You can also add .png next to .css / .js to serve your logo/icons or other resources. And Far future conf help to expire your resource at browser later and suggested to turn on.

To support far future, you will need below in nginx config, so that your CDN can curl the far future path. Reference:

### CDN Far Future expiration support.
location ^~ /cdn/farfuture/ {
tcp_nodelay   off;
access_log    off;
log_not_found off;
etag          off;
gzip_http_version 1.0;
if_modified_since exact;
location ~* ^/cdn/farfuture/.+\.(?:css|js|jpe?g|gif|png|ico|bmp|svg|swf|pdf|docx?|xlsx?|pptx?|tiff?|txt|rtf|class|otf|ttf|woff|eot|less)$ {
expires max;
add_header X-Header “CDN Far Future Generator 1.0”;
add_header Cache-Control “no-transform, public”;
add_header Last-Modified “Wed, 20 Jan 1988 04:20:42 GMT”;
rewrite ^/cdn/farfuture/[^/]+/[^/]+/(.+)$ /$1 break;
try_files $uri @nobots;
location ~* ^/cdn/farfuture/ {
expires epoch;
add_header X-Header “CDN Far Future Generator 1.1”;
add_header Cache-Control “private, must-revalidate, proxy-revalidate”;
rewrite ^/cdn/farfuture/[^/]+/[^/]+/(.+)$ /$1 break;
try_files $uri @nobots;
try_files $uri @nobots;

After you do all above, you can have CDN run smoothly and also cloud storage + CDN for Drupal 🙂 Or use bootdev, one click, all set.


Why cloudfront / CDN ?

  1. Faster your content to audience
  2. Lower your AWS network traffic cost
  3. Available world wide
  4. Lower your Server CPU/Memory as requests share to CDN

Why BootDev — For website builders’ Backend as a service (wBaaS)

Recently, we launched a project, bootdev which deploys a configured Drupal site (Drucloud) into a pre given architecture into users’ personal AWS account. We call it website backend as a service (wBaaS). With this approach, we can deliver our hard earned experience to other business owner or developers without REWORK of what we did.

What we deliver is just configuration and knowledge. After the deployment, we dont host it, users’ host their site in their own AWS. We also provide a technical foundation / playground for people to add features /  experience best practice(s).

Currently available with Drupal 7.

With just 1 click, you can have pre configured like:

  • Caching
    • Nginx micro-cache
    • Cache pre-warmer
    • PHP APC
    • Memcache (AWS ElastiCache)
    • MySQL Query Cache
  • Database
    • AWS RDS configuration for Drupal
    • Multi-AZ
    • Secure by under AWS VPC
  • Web server
    • Nginx Drupal configuration
      • advagg css / js compression support
      • Image style catching
      • Micro cache
      • AWS Cloudfront CDN support (far future expire)
      • Cache control headers
    • PHP-FPM
      • APC
      • Max client configurations under EC2 m3.large
  • SOLR Search
    • SOLR index cron job
    • Drupal SOLR integration
  • DevOps
    • Cloudformation
    • Chef
    • Auto-scaling
    • Git deploy with bitbucket private repo + deploy key
  • Maintenance
    • System maintenance cron job
    • xmlsitemap
    • social network stat
  • Email
    • SPAM email control
    • Mass mail support (AWS SES support)
  • MAP
    • Google Map integration
  • Social
    • Social network meta-tag
  • File handling
    • Drupal S3 integration
    • Push to CDN
    • Separate file handling with other Drupal services
  • CDN
    • Drupal CDN configuration
    • Suport CORs
  • Server architecture
    • 2* EC2 m3.large web server
    • 1* EC2 m1.small chef server
    • 1* Mutli-AZ m3.large RDS
    • 1* EC2 m3.large SOLR server
    • 2* ElastiCache node m1.medium
    • Cloudfront CDN
    • Amazon S3
    • Auto-scaling with ELB
    • 2* EC2 m1.small GlusterFS
    • Inside VPC

In coming topics, I will explain why you need each of those configurations to make your site awesome.


fastcgi with apache, virtualhost per user setup

Im showing a working conf for fastcgi per user virtualhost & php.ini on apache2

Googled some reference with lots of apache like putting FcgidInitialEnv into virtualhost does not work. Tested out a working conf for fastcgi with apache, virtualhost per user by suExec setup.

About installing dependencies on CentOS 6, reference below ***Remember to add the epel repo

Beware suexec not allow to set uid < 500 ===> you cannot use apache (CentOS) www-data(Ubuntu) in SuexecUserGroup

Just add below apache conf in /etc/httpd/conf.d/anyfilename.conf, this conf run a php wrapper by FCGIWrapper

Then the wrapper can specify which php.ini to run

<IfModule fcgid_module>
  FcgidInitialEnv PHPRC "/php"

# using mod_fcgid and user specific php.ini
# You can copy this part, add user and create more virtualhost on demand
<VirtualHost *:80>
  DocumentRoot "/var/www/keithyau"
  SuexecUserGroup keithyau keithyau

  <Directory /var/www/keithyau>
    Options FollowSymLinks +ExecCGI
    AllowOverride All
    AddHandler fcgid-script .php
    FCGIWrapper /var/www/change-this/fcgi1/fcgi1-wrapper .php
    Order allow,deny
    Allow from all

The fcgi1-wrapper is something like this, PHPRC is the folder to contain custom php.ini you can add more settings.

  export PHPRC
  exec /var/www/html/fastcgi-bin/php-cgi

Screen Shot 2014-03-12 at 6.20.10 PM


We can config like disable_function in this php.ini. Finally, check the loaded phpinfo

Complete solution to bypass GFW (SQUID + SSH Tunnel + AWS)

I used single solution to bypass GFW for a long time. Running VPN, SSH Tunnel, SQUID … Each of them got pros and cons. And sometimes not smooth. Especially I want to watch Videos that only free licensing in China, but also facebook(ing). VPN cant help.

While the power of GFW increasing, SSH Tunnel & SQUID sometimes will be de-functionated. Recently, I use the combination of SSH Tunnel + SQUID as my solution and works well.

For junior technical people, the idea is a bit complex. What I am doing is SSH encrypt SQUID requests. (Mapping the remote SQUID port to a new created local port underlining SSH)

There is free one AWS EC2 available for 2012 subscription, so I use AWS for my remote server.

SSH Tunnel
localhost:22+————————————-+ 22:remote
  +  +
  + +
   8080+  + 3128 (Remote SQUID on AWS)


With this setup, I can use FoxyProxy (Google Chrome) or AutoProxy (Firefox) help to forward all request from browser to local binded SQUID port (8080). So, I can enjoy http/https requests but not SOCK5 and those requests are encrypted, GFW wont drop the request by filtering keywords.

To establish the tunnel above, the easiest way in Windows system is using bitvise. With the attached file setting. (Of coz, with your AWS key and able to login)