使用 Nginx 反代 Apache 安装 WordPress

最近由于原先博客主机极度不稳定,所以准备了很久,准备进行主机迁移。由于迁移前的环境和迁移后还是有较大的区别,整体架构也不太一样,所以在这里说说迁移过程中遇到的问题。

环境对比

原主机 新主机
操作系统 CentOS 6 CentOS 7
Web 服务器 Apache 2.2 Openresty 1.9.7 + Apache 2.4
PHP 版本 5.5 5.6
其他 SELinux

SELinux

很多人为了省事,在拿到主机的第一时间就直接禁用了 SELinux。不过在学习了一段时间之后,我发现其实 SELinux 是一个很好的保护机器的手段。

这里简单列举几个需要注意的 SELinux 的配置:

 

httpd_can_network_connect_db boolean 类型,控制 Apache 是否可以连接 DB
http_port_t port 类型,控制 Apache 可以监听的端口
mysqld_port_t port 类型,控制 mysql 可以监听的端口和 Apache 可以连接的 DB 端口

由于我是将 Apache 作为只解析后端 PHP 请求使用,所以需要修改 http_port_t 加入我需要的端口。添加方法类似于:

semanage port -a -t http_port_t  -p tcp 8090

另外由于我没有在本机安装 Mysql 而是使用的远程 Mysql 实例,并且开放的端口并不是标准的 3306,所以需要将端口号添加到 mysqld_port_t 中。

Apache

这玩意是主要的坑所在。下面来一一列举。

配置格式变更

Apache 2.2 和 2.4 的配置文件区别还是比较大的,加了很多新的参数,同时修改了很多配置的方法。最明显的是:

Options -Indexes

Allow from all
Deny from all

这三条配置已经完全被改掉了。如果在配置中出现第一种,会直接起不来。后面的 Allow Deny 的写法虽然不会有问题,但是已经不是官方推荐的了,建议改掉。

配置无效

在配置 Apache 2.4 的 Log Format 的时候,我发现了一个很蛋疼的问题,就是 CentOS yum 安装的版本(2.4.6)有些配置是使用不了的。如:

http://httpd.apache.org/docs/2.4/mod/mod_log_config.html

这个文档中的:

%{UNIT}T	The time taken to serve the request, in a time unit given by UNIT. Valid units are ms for milliseconds, us for microseconds, and s for seconds. Using s gives the same result as %T without any format; using us gives the same result as %D. Combining %T with a unit is available in 2.4.13 and later.

很多参数都有类似的标注(available in 2.4.13 and later),告诉你在旧版本中不能使用。如果不仔细看的话很容易忽略。所以配置之前一定要仔细阅读。

HTTPS

WordPress

由于我的博客是全站 HTTPS 的,因此在 WordPress 上我是有做一些强制 HTTPS 的措施。但是!由于 WordPress 默认的检测 HTTPS 的方法是这样的:

/**
 * Determine if SSL is used.
 *
 * @since 2.6.0
 *
 * @return bool True if SSL, false if not used.
 */
function is_ssl() {
        if ( isset($_SERVER['HTTPS']) ) {
                if ( 'on' == strtolower($_SERVER['HTTPS']) )
                        return true;
                if ( '1' == $_SERVER['HTTPS'] )
                        return true;
        } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
                return true;
        }
        return false;
}

而我的 HTTPS 是在 Nginx 层做的,所以导致这两个条件均不满足,因此会遇到重定向循环(Redirect Loop)的问题。解决方法有两种:

修改 wp-config.php 文件:

if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && ( 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
    $_SERVER['HTTPS'] = 'on';
}

这里的修改需要配合修改 Nginx 的 Proxy 设置,增加下面这行:

proxy_set_header X-Request-Protocol $scheme; #http or https

修改 wp-includes/functions.php 文件(4025行左右):

找到上面说的那段代码,将其替换为:

/**
 * Determine if SSL is used.
 *
 * @since 2.6.0
 *
 * @return bool True if SSL, false if not used.
 */
function is_ssl() {
        if ( isset($_SERVER['HTTPS']) ) {
                if ( 'on' == strtolower($_SERVER['HTTPS']) )
                        return true;
                if ( '1' == $_SERVER['HTTPS'] )
                        return true;
        } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
                return true;
        } elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && ( 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
                return true;
        }
        return false;
}

同时这个修改方法也要配合修改 Nginx 的 Proxy 设置。

注意,虽然可以不做判断直接无脑 $_SERVER[‘HTTPS’] = ‘on’; 或者在那个 is_ssl() 方法中无脑返回 true,但是不建议这样修改。因为这样可能会导致安全上面的问题。

Apache .htaccess

由于我在某些目录中通过 .htaccess 的方式强制重定向了非 HTTPS 请求,因此在将 SSL 交给 Nginx 之后相关的跳转判断也要修改。原先的跳转逻辑是:

RewriteEngine on
RewriteCond   %{HTTPS} !=on
RewriteRule   ^(.*)  https://%{SERVER_NAME}/$1 [L,R]

由于 %{HTTPS} 这个变量判断的是 Apache 传进来的内部参数,我们无法控制,所以需要将这段修改为:

RewriteEngine on
RewriteCond %{HTTP:X-Request-Protocol} ^http$
RewriteRule   ^(.*)  https://%{SERVER_NAME}/$1 [L,R]

这里判断的就是 Nginx 传进来的 X-Request-Protocol 头了。不过这种写法并不能在单独使用 Apache 作为 Web 服务器的时候使用,需要注意。

客户端证书

目前只针对某目录或文件的客户端证书配置还没有测试完成,将在后面更新。

使用 stunnel 加密原本不支持加密的连接

之前在国内的 TX 云主机上安装了 cow 作为连接国外 ss 的跳板。最近由于 Wifi 安全问题被各种关注,就想到 cow 本身是只支持 http 协议的。也就是说,我跟这个代理的任何通信理论上都能被同一 AP 下的其它机器截获。

为了解决这个问题,同时也作为某些公众 Wifi 禁止连接 VPN 的解决方案,我使用了 stunnel 来将 cow 变为支持 https 的代理服务器。

PS:之所以使用 cow 而不是其它的专业代理服务器,是因为在使用 cow 的时候可以无缝连上国外网站,无痛上 Google ,所以。。。

下面说说配置过程:

因为 stunnel 已经发布到了 epel 源中,所以如果你的 CentOS 添加了 epel 源的话,可以直接 yum install stunnel. 如果想自己编译也不困难,下载源码 ./configure && make && make install 即可。下面主要说说配置:

如果你是用的 epel 源的 stunnel ,你会发现安装包已经自动建立了 /etc/stunnel/ 目录,只不过里面啥都没有。所以我们要先创建配置文件:

vim /etc/stunnel/stunnel.conf

写入如下内容:

cert = /etc/stunnel/stunnel.pem
chroot = /var/run/stunnel
setuid = nobody
setgid = nobody
pid = /stunnel.pid
debug = 7
options = NO_SSLv2
fips = no
compression = zlib

[cow]
accept = 8080
connect = 127.0.0.1:7777

其中[cow]段可以多次重复,这样的话可以一个配置文件加密多个程序,使用端口号区分。
注意,fips = no 如果不加上的话会出现这个错误:

FIPS_mode_set: 2D06C06E: error:2D06C06E:FIPS routines:FIPS_module_mode_set:fingerprint does not match

接下来写一个启动脚本放到 /etc/init.d/ 中:

vim /etc/init.d/stunnel
#!/bin/bash
#
# Script to run stunnel in daemon mode at boot time.
#
# Check http://www.gaztronics.net/ for the
# most up-to-date version of this script.
#
# This script is realeased under the terms of the GPL.
# You can source a copy at:
# http://www.fsf.org/copyleft/copyleft.html
#
# Please feel free to modify the script to suite your own needs.
# I always welcome email feedback with suggestions for improvements.
# Please do not email for general support. I do not have time to answer
# personal help requests.

# Author: Gary Myers MIIE MBCS
# email: http://www.gaztronics.net/webform/
# Revision 1.0 - 4th March 2005

#====================================================================
# Run level information:
#
# chkconfig: 2345 99 99
# description: Secure Tunnel
# processname: stunnel
#
# Run "/sbin/chkconfig --add stunnel" to add the Run levels.
# This will setup the symlinks and set the process to run at boot.
#====================================================================

#====================================================================
# Paths and variables and system checks.

# Source function library (It's a Red Hat thing!)
. /etc/rc.d/init.d/functions

# Check that networking is up.
#
[ ${NETWORKING} ="yes" ] || exit 0

# Path to the executable.
#
SEXE=`which stunnel`

# Path to the configuration file.
#
CONF=/etc/stunnel/stunnel.conf

# Check the configuration file exists.
#
if [ ! -f $CONF ] ; then
  echo "The configuration file cannot be found!"
exit 0
fi

CHROOT=`grep '^chroot' /etc/stunnel/stunnel.conf | head -n 1 | sed 's/ //g' | awk -F= '{ print $2 }'`
PIDFILE=`grep '^pid' /etc/stunnel/stunnel.conf | head -n 1 | sed 's/ //g' | awk -F= '{ print $2 }'`
if [ -n "$CHROOT" ]; then
    PIDFILE=$CHROOT/$PIDFILE
fi

# Path to the lock file.
#
LOCK_FILE=/var/lock/subsys/stunnel

#====================================================================

#====================================================================
# Run controls:

prog=$"stunnel"

RETVAL=0

# Start stunnel as daemon.
#
start() {
  if [ -f $LOCK_FILE ]; then
    echo "stunnel is already running!"
    exit 0
  else
    echo -n $"Starting $prog: "
    $SEXE $CONF
  fi

  RETVAL=$?
  [ $RETVAL -eq 0 ] && success
  echo
  [ $RETVAL -eq 0 ] && touch $LOCK_FILE
  return $RETVAL
}


# Stop stunnel.
#
stop() {
  if [ ! -f $LOCK_FILE ]; then
    echo "stunnel is not running!"
    exit 0

  else

    echo -n $"Shutting down $prog: "
    killproc -p $PIDFILE stunnel
    RETVAL=$?
    [ $RETVAL -eq 0 ]
     rm -f $LOCK_FILE
    echo
    return $RETVAL

  fi
}

# See how we were called.
case "$1" in
   start)
  start
  ;;
   stop)
  stop
  ;;
   restart)
  stop
  start
  ;;
   condrestart)
  if [ -f $LOCK_FILE ]; then
     stop
     start
     RETVAL=$?
  fi
  ;;
   status)
  status -p $PIDFILE stunnel
  RETVAL=$?
  ;;
   *)
    echo $"Usage: $0 {start|stop|restart|condrestart|status}"
    RETVAL=1
esac

exit $RETVAL

保存之后不要忘了给执行权限。
接下来就要配置证书了。将你的证书、私钥、CA证书按照如下顺序合并:

cat server.crt server.key ca.crt >/etc/stunnel/stunnel.pem

然后因为大部分人的私钥都是明文的,所以要注意这个文件的权限。如果文件权限比较危险的话 stunnel 会有提示。

chmod 600 /etc/stunnel/stunnel.pem

最后就是创建 stunnel 需要的临时文件夹并赋予权限了。

mkdir -p /var/run/stunnel/
chown nobody:nobody /var/run/stunnel

这个用户和组按照 stunnel.conf 中所写的来授权。
大功告成!来运行一下吧:

service stunnel start

使用 netstat -ntlp 来看看是否正常开始监听端口。如果监听正常就没有问题啦~

博客正式开启全站强制SSL

由于 Google 宣即将在Chrome浏览器中全面将HTTP网站标记为不安全(http://www.chromium.org/Home/chromium-security/marking-http-as-non-secure),我很早就想将博客开启全站HTTPS了。不过由于我的主机带宽不够,而且地理位置是在香港,所以无法在开启全站HTTPS之后提供较快的图片下载速度。而国内的CDN支持HTTPS的又少的可怜,只好开启了后台HTTPS,但是前台页面都没有开启HTTPS。
今天收到邮件看到七牛云存储做活动,发布了海外加速服务,于是就顺手去看了一眼。没想到无意中发现七牛已经开始给CDN部署HTTPS了。虽然只能使用自定义域名,但是对我没有影响,因为我是使用WP Super Cache插件将博客页面中的资源文件的链接在输出的时候直接替换成CDN的链接的,所以终于可以真正的开启全站HTTPS了。
现在再访问我的博客,如果使用HTTP访问,会被强制302到HTTPS的链接。在这个页面里查看源文件的话可以看到,资源文件都是从
https://dn-maoxian.qbox.me/
这个域名下载的。这就是七牛云存储的CDN域名。有了这个,再也不用担心HTTPS的页面什么CSS都加载不了被浏览器直接Block啦~

附上我的七牛邀请链接:

https://portal.qiniu.com/signup?code=3l7i9c6vk7qky

PS:不过七牛不好的一点是想得到免费额度需要身份认证。。。不过。。。这是国内CDN的通病了。。。没办法。。。这个就要自己权衡了~

Chrome下强制http重定向到https的方法

使用Google的https搜索的时候,我们会发现搜索结果虽然可以显示,但是有时候结果的链接却无法打开。这里的原因是因为Google的https的搜索结果链接往往使用的是http的方式打开,因此有时候会无法访问,本文将介绍在Chrome下解决这个问题的方法。

如果你使用的是Chrome浏览器,只需要经过一些简单的设置,就可以强制Google的https搜索结果链接也使用https的方式打开。

打开Chrome,在地址栏输入chrome://net-internals/

之后,在HSTS选项卡下的Domain中输入 www.google.com (或 www.google.com.hk ),然后点击Add按钮。

3270_2

现在你再使用Google的Https搜索,就会发现搜索结果的链接可以打开了。

如果想针对别的网站启用强制HTTPS,只需要将域名添加进去就可以了。