Ghost 是由 John O’NolanHannah Wolfe 创立的开源博客平台。Ghost 由 node.js 所驱动,因此配合上 nginx 有着非凡的表现。这篇文章将会帮助你编写一个高性能 nginx 虚拟主机配置方案使它能和 Ghost 一起高效运作。

“不要在处理静态资源上使用 nodejs ”—— @trevnorris。如果你的 node 服务器没有 nginx 作为前端服务器,那么你很可能正在做着错误的事。

——Bryan Hughes (@nebrius)

在看到这条推文之后,我决定开始编写我自己的 nginx 配置文件来使得它能和 Ghost 一起高效运作。

Ghost 是运行在一个服务器端口上的 node.js 应用,我们可以用 nginx 来反向代理这个端口,从而不需要依靠 node web 应用框架 express。首先,我们需要定义一个 upstream 来告诉 nginx Ghost 运行在哪个端口上:

upstream ghost_upstream {  
    server 127.0.0.1:2368;
    keepalive 64;
}

这段配置告诉 nginx Ghost 运行在 127.0.0.1:2368 上,同时设定连接保持 64 秒以避免每个请求都要重建连接。

代理缓存

我们希望 nginx 能缓存下来自 Ghost 的响应,从而避免每个(来自客户端)请求都通过反向代理再次向 Ghost 应用发起请求。这时要做的第一件事是在配置文件中设置 proxy_cache_path。下面这段配置设定了一个 75MB 的内存空间来缓存来自 Ghost 的响应,并且会在 24 小时后移除那些没有被再次请求的文件:

proxy_cache_path /var/run/cache levels=1:2 keys_zone=STATIC:75m inactive=24h max_size=512m;  

注:

  1. levels 指定目录结构,可以使用任意的 1 位或 2 位数字作为目录结构,如 11:21:1:2,但是最多只能是三级目录。
  2. 所有活动的 key 和元数据存储在共享的内存池中,这个区域用 keys_zone 参数指定。STATIC 指的是共享池的名称,75m 指的是共享池的大小。
  3. inactive 参数指定缓存有效时间,如果在指定的时间内缓存的数据没有被请求则会被删除,默认 inactive 为 10 分钟。
  4. 一个名为 cache manager 的进程控制磁盘的缓存大小,它被用来删除不活动的缓存和控制缓存大小,这些都在 max_size 参数中定义,当目前缓存的值超出 max_size 指定的值之后,超过其大小后最少使用数据(LRU替换算法)将被删除。

    ——Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解

Server 字段

现在我们可以开始配置你的 Ghost 博客的域名信息了。注意:如果你在使用 SSL/TLS 来访问你的博客,你需要留意本文末尾的指南

1. 处理博客页面的 location 字段

下面这个配置将根据对来自 upstream 的 200 响应进行 30 分钟的缓存,对 404 响应进行一分钟的缓存,缓存写入 STATIC 区域。我们同样也希望能够忽略和(或)隐藏掉几条来自 Ghost 的响应头,因为我们将使用自己的 header 信息来取代它。除了在 nginx 上缓存下这些请求,我们同样会在浏览器中进行 10 分钟的缓存,expires 10m;

location / {  
    proxy_cache STATIC;
    proxy_cache_valid 200 30m;
    proxy_cache_valid 404 1m;
    proxy_pass http://ghost_upstream;
    proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
    proxy_ignore_headers Set-Cookie;
    proxy_hide_header Set-Cookie;
    proxy_hide_header X-powered-by;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    expires 10m;
}

Dolphin 注:

  1. Ghost 会在资源请求的响应头上加上 Cache-Control: public, max-age=0,参见 Static Asset Management
  2. Ghost 会在所有请求的响应头上加上 Set-Cookie存在此字段时,nginx 不会缓存文件

如果你希望通过响应头来告诉客户端资源缓存状态,可以加上

add_header X-Cache $upstream_cache_status;  

2. 处理静态文件的 location 字段

在这个部分,我们将要告诉 nginx 去哪儿找到 Ghost 应用的静态文件,诸如 css、js 和图片。这里需要使用四个 location 字段来分别指向 images 目录、assets 目录、public 目录 和 scripts 目录。我们将使用 expires max; 来缓存这些文件,以便它们能在客户端上永久保存下来。不要担心这样做会来带什么危害,因为当 node.js 初始化时,Ghost 会在每个静态文件(不包括 images 目录下的文件)后面加上一个版本号。

注:

Ghost 主题文件中对静态文件的引用都应该是 {{asset xxxx.xxx}} 形式,也就是用 Ghost 提供的 assets 指令,这样就能为每个静态文件附加一个时间戳了。

在现版本中,Ghost 为静态文件所添加的时间戳是 Ghost 版本号 + Ghost 系统启动时的时间 的 MD5 值,也就是说,你每重启一次 Ghost,时间戳就会改变。

——让 nginx 直接分发 Ghost 中的静态文件

注意:当改变你的 Ghost 主题时,你需要改变 location /assets 字段中的资源目录。

location /content/images {  
    alias /path/to/ghost/content/images;
    access_log off;
    expires max;
}
location /assets {  
    alias /path/to/ghost/content/themes/(theme-name)/assets;
    access_log off;
    expires max;
}
location /public {  
    alias /path/to/ghost/core/built/public;
    access_log off;
    expires max;
}
location /ghost/scripts {  
    alias /path/to/ghost/core/built/scripts;
    access_log off;
    expires max;
}

完整的 nginx 配置

如果你按照我们的指南一步步做下来了,现在你的 nginx 配置看起来会是这样:

proxy_cache_path /var/run/cache levels=1:2 keys_zone=STATIC:75m inactive=24h max_size=512m;

server {  
   server_name domain.com;
   add_header X-Cache $upstream_cache_status;
   location / {
        proxy_cache STATIC;
        proxy_cache_valid 200 30m;
        proxy_cache_valid 404 1m;
        proxy_pass http://ghost_upstream;
        proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
        proxy_ignore_headers Set-Cookie;
        proxy_hide_header Set-Cookie;
        proxy_hide_header X-powered-by;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        expires 10m;
    }
    location /content/images {
        alias /path/to/ghost/content/images;
        access_log off;
        expires max;
    }
    location /assets {
        alias /path/to/ghost/content/themes/uno-master/assets;
        access_log off;
        expires max;
    }
    location /public {
        alias /path/to/ghost/core/built/public;
        access_log off;
        expires max;
    }
    location /ghost/scripts {
        alias /path/to/ghost/core/built/scripts;
        access_log off;
        expires max;
    }
    location ~ ^/(?:ghost|signout) { 
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://ghost_upstream;
        add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";
    }
}

SSL/TLS 配置

如果你希望通过 HTTPS 来访问你的博客,那么你要做的第一件事就是改变 Ghost 的配置文件 config.js 里的 URL 值。

nginx 上的 SSL/TLS 配置需要数条额外的项目。本章节的末尾有一个样例,我在这里强调一下重要的不同点。

在下面的配置里,最初的几行建立并优化了 HTTPS 连接。你可以使用 SPDY 和它的几个选项如 spdy_headers_compkeepalive_timeoutssl_session_cache 以及 OCSP。这里我假定你已经知道了这些,因为我们的目的是讨论 Ghost(笑)。

一件重要的事是,在 location / 字段里设定

proxy_set_header X-Forwarded-Proto https;  

否则在你访问你的 Ghost 博客时,你将遭遇到循环重定向。同样你也需要在 location ~ ^/(?:ghost|signout) 包含上这些内容。

server {  
   server_name domain.com;
   listen 443 ssl spdy;
   spdy_headers_comp 6;
   spdy_keepalive_timeout 300;
   keepalive_timeout 300;
   ssl_certificate_key /etc/nginx/ssl/domain.key;
   ssl_certificate /etc/nginx/ssl/domain.crt;
   ssl_session_cache shared:SSL:10m;  
   ssl_session_timeout 24h;           
   ssl_buffer_size 1400;              
   ssl_stapling on;
   ssl_stapling_verify on;
   ssl_trusted_certificate /etc/nginx/ssl/trust.crt;
   resolver 8.8.8.8 8.8.4.4 valid=300s;
   add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
   add_header X-Cache $upstream_cache_status;
   location / {
        proxy_cache STATIC;
        proxy_cache_valid 200 30m;
        proxy_cache_valid 404 1m;
        proxy_pass http://ghost_upstream;
        proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
        proxy_ignore_headers Set-Cookie;
        proxy_hide_header Set-Cookie;
        proxy_hide_header X-powered-by;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Host $http_host;
        expires 10m;
    }
    location /content/images {
        alias /path/to/ghost/content/images;
        access_log off;
        expires max;
    }
    location /assets {
        alias /path/to/ghost/themes/uno-master/assets;
        access_log off;
        expires max;
    }
    location /public {
        alias /path/to/ghost/built/public;
        access_log off;
        expires max;
    }
    location /ghost/scripts {
        alias /path/to/ghost/core/built/scripts;
        access_log off;
        expires max;
    }
    location ~ ^/(?:ghost|signout) { 
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://ghost_upstream;
        add_header Cache-Control "no-cache, private, no-store,
        must-revalidate, max-stale=0, post-check=0, pre-check=0";
        proxy_set_header X-Forwarded-Proto https;
    }
}

Dolphin 补充:

  1. SSL 证书生成及配置:Setting up SSL with nginx (using a NameCheap EssentialSSL wildcard certificate on DigitalOcean)
  2. 为 nginx 开启 SPDY 支持:
    1. 通过编译安装:./configure --with-http_ssl_module --with-http_spdy_modulesudo make & make install
    2. 通过 apt-get:sudo add-apt-repository ppa:nginx/stableapt-get update & apt-get upgrade nginx

(全文完)