I almost forgot how to use Apache

我不用 Apache 很多年,几乎忘了该怎么完整地配一台 Apache server。最近上线了一台 FreePBX,我想加强 web server 的安全系数,要修改一些默认参数。因为对 FreePBX 还不是很了解,所以不敢贸然把它的 web server 用我喜欢的 Nginx 代替,只好沿用元配的 Apache,捣腾它的 conf 文件。

首先,我想弄个 VirtualHost,把 FreePBX admin 界面隐藏到不公开的 ServerName 上。我忘了在使用 name based virtual host 前要先使用 NameVirtualHost 先声明一下。


NameVirtualHost ip_address:port_number

其次,启用了 VirtualHost 以后,VirtualHost 之外的 main DocumentRoot 就不起作用了。我在 VirtualHost 之外定义了一个 DocumentRoot 并想当然地认为它是 default server (未经任何一个 VirtualHost 定义的 server)的 DocumentRoot。事实上,VirtualHost 的 default server 是由 _default_ 显式定义,或在 _default_ 缺失的情况下,第一个 VirtualHost 就是 default server。

1and1 cloud server datasheet

从 1&1 新订了一个合同,cloud server,就是为了让 magento 跑快一些。那 1and1 的 cloud server 究竟能有多快呢?

先看看 cat /proc/cpuinfo 的情况
processor : 0
vendor_id : AuthenticAMD
cpu family : 16
model : 2
model name : Quad-Core AMD Opteron(tm) Processor 2352
stepping : 3
cpu MHz : 2109.718
cache size : 512 KB
fpu : yes
fpu_exception : yes
cpuid level : 4
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
bogomips : 4219.43
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 48 bits physical, 48 bits virtual
power management:

processor : 1
vendor_id : AuthenticAMD
cpu family : 16
model : 2
model name : Quad-Core AMD Opteron(tm) Processor 2352
stepping : 3
cpu MHz : 2109.718
cache size : 512 KB
fpu : yes
fpu_exception : yes
cpuid level : 4
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
bogomips : 4220.53
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 48 bits physical, 48 bits virtual
power management:

processor : 2
vendor_id : AuthenticAMD
cpu family : 16
model : 2
model name : Quad-Core AMD Opteron(tm) Processor 2352
stepping : 3
cpu MHz : 2109.718
cache size : 512 KB
fpu : yes
fpu_exception : yes
cpuid level : 4
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
bogomips : 4223.97
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 48 bits physical, 48 bits virtual
power management:

processor : 3
vendor_id : AuthenticAMD
cpu family : 16
model : 2
model name : Quad-Core AMD Opteron(tm) Processor 2352
stepping : 3
cpu MHz : 2109.718
cache size : 512 KB
fpu : yes
fpu_exception : yes
cpuid level : 4
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
bogomips : 4219.25
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 48 bits physical, 48 bits virtual
power management:

在 cloud server 上我使用 zend server。我特意从 1 Virtual processor core 1GB Ram 一步步往上加,使用 ab -c 5 -n 500 对比测试,客户端是 10 Mb down stream / 1Mb up stream adsl 连接。

在 1 Virtual processor core 1GB Ram 时,Requests per second: 5.31 [#/sec]
在 2 Virtual processor cores 1GB Ram 时,Requests per second: 8.34 [#/sec]
在 2 Virtual processor cores 2GB Ram 时,Requests per second: 8.26 [#/sec]
在 3 Virtual processor cores 1GB Ram 时,Requests per second: 10.57 [#/sec]
在 4 Virtual processor cores 1GB Ram 时,Requests per second: 11.33 [#/sec]

在 shopping cart 里有 7 条不同商品时,checkout/cart/index 页面时间为 10 秒左右。

回头看看老 server
# cat /proc/cpuinfo
processor : 0
vendor_id : AuthenticAMD
cpu family : 15
model : 67
model name : Dual-Core AMD Opteron(tm) Processor 1216 HE
stepping : 3
cpu MHz : 1000.000
cache size : 1024 KB
physical id : 0
siblings : 2
core id : 0
cpu cores : 2
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 1
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
bogomips : 1999.96
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management: ts fid vid ttp tm stc

processor : 1
vendor_id : AuthenticAMD
cpu family : 15
model : 67
model name : Dual-Core AMD Opteron(tm) Processor 1216 HE
stepping : 3
cpu MHz : 1000.000
cache size : 1024 KB
physical id : 0
siblings : 2
core id : 1
cpu cores : 2
apicid : 1
initial apicid : 1
fpu : yes
fpu_exception : yes
cpuid level : 1
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
bogomips : 1999.96
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management: ts fid vid ttp tm stc

运行的是 nginx,同等条件下,Requests per second: 8.12 [#/sec],checkout/cart/index 页面生成时间也是 10 秒左右。

虽 zend server 和 nginx 不同,靠不是完全对等的对比结果,我还是可以得出两个结论:

  1. 瓶颈仍是 cpu 速度;
  2. cloud server 并未显著提高速度,我对它预期过高,略有失望。

Avoid PEM pass phrase

我在制作 SSL key file 时输入了一个 pass phrase。CA 把 SSL 证书发给我后,我在 Nignx 试着加载 key 和 证书,发现每次重启 Nginx 时,都会被要求 Enter PEM pass phrase。岂不很烦,而且万一服务器重启,岂不还要人工干预才能重启 web server?

原本以为把 pass phrase 从 key 文件里拿掉后,要找 CA 重新制作证书,后来发现不用,证书跟 pass phrase 无关。Nginx 的文档没有提及,Apache 倒是有提:

If necessary, you can also create a decrypted PEM version (not recommended) of this RSA private key with:

openssl rsa -in server.key -out server.key.unsecure

拿到 pass phrase 后安全性自然降低了,不过完全值得。

Create a password file for Nginx basic authentication

It is really a hard time for me to find a way to create a password file for Nginx basic authentication, because I did not realise htpasswd crpty is not available on Windows.

Nginx documentation only mentions Use crypt(3) encryption for passwords, so I tried so hard with htpasswd.exe but could not generate a file recognised by Nginx. Hours later I found Apache documentation mentions -d is

the default on all platforms but Windows, Netware and TPF. Though possibly supported by htpasswd on all platforms, it is not supported by the httpd server on Windows, Netware and TPF.

I have two things to blame –

  1. I still use Windows to do my everyday work. If my first choice was htpasswd on Linux even without furthur instructions, it would have save me a lot of time.
  2. Nginx documentionation. If Nginx documentation is as good as Apache’s, Nginx may overwhelm the world.

Perfect settings for Magento on Nginx

Nginx 的文档不够详细,挺折腾人的。我经过两天181次黑匣试验,终于敢说入了 Nginx 的门。摸清 Nginx 的思路以后,才体会到它的先进性(不仅是效率上的)。php 和 nginx 搭配使用,感觉就不那么草根了,更容易借鉴 python 的 web infrastructure.

从 Apache 到 Ngnix,不容易啊。难就难在要抛弃 Apache 的思维,其实同样的配置在 Nginx 总能做到的,而且更简洁。在Apache 下,我用 symbolic link 实现 Magento 和 WordPress 等一次安装多处使用,当时我认为已经是很简单的解决方案了;转移到 Nginx 平台,多处使用连 symblic link 也省了。

以下是 Magento on Nginx 的配置,perfect,可以以不变应万变,目前我挺得意的。别笑我,这么几行配置,折腾我两天。

location / {

root  $php_doc_root;

index  index.php;

if ($uri ~ ^/(media|js|skin)/) {

break;

}

if (!-e $request_filename) {

rewrite .* /index.php last;

}

}

location ~ ^/(app|lib|var)/ {

deny all;

}

location ~ ^/report/.*\.xml {

deny all;

}

location ~* ^.+\.(jpg|jpeg|gif|css|png|js|ico)$ {

root  $php_doc_root;

index  index.php;

access_log        off;

expires           30d;

}

Different syntax in Nginx and Apache

今天发现一个 Nginx 和 Apache 看似细微却影响巨大的语法差异:

在 Apache 里重定向一个域名到另一个域名,

RewriteRule (.*) http://destination.com/$1 [r=301,l]

而在 Nginx 里,应该多加一个斜杠/,否则 $1就多了一个斜杠。

rewrite ^/(.*) http://destination.com/$1 permanent;

From Apache to Nginx

我感觉配置 nginx 渐入佳境了。配置 nginx 时应该用不同 apache 的思维,最主要的不同点是:

apache 是 fallback 式配置,比如有两个 <directory> block,分别是 mydirectory 和 mydirectory/subdir。mydirectory/subdir 可以重写 mydirectory 的 directives,但 mydirectory/subdir 没有提到的 directives 就沿袭 mydirectory 的 directives。

nginx 是 parallel 式配置,location 只能按优先规则取其一。

另外,我觉得有人批评 apache 层层检测 .htaccess 造成效率低下的一个原因。我感觉这种说法并不妥,很多 apache 发行版默认 allowoverride none,用户只要根据需要在确实需要启用 .htaccess 的那一层启用 allowoverride,这并不造成效率损失,反而带来配置的灵活性。当然,nginx 不需要这种灵活性,因为它不是面向 share hosting 的产品。

除了内存占用大是 apache 的硬伤,其他 nginx 的优势并不明显,apache 同样可以优化配置达到相应的效果,尽管很多配置不是 apache 的默认配置。(这只是我的总体感觉,我并没有深入去比较,欢迎探讨。)

nginx has no path_info

ngnix 0.6.35 没有实现 path_info。不知道 nginx 开发者是疏忽了,还是觉得没必要在 http server 层实现 path_info。我印象中有很多 php 程序的运行都依赖 $_SERVER[‘PATH_INFO’],所以在 ngnix proxy pass 给 php-cgi 之前,必须设置好正确的 path_info 参数。

我认为最好的办法还是用正则表达式在 $fastcgi_script_name 里隔离出 script_name 和 path_info,但这样计算出的 path_info 用起来不如 ngnix variable 方便,而且得在脚本的安装位置或扩展名确定时才能写出相应的正则表达式。我是 nginx 新手,在 path_info 上折腾了三天才总结出一个相对满意的方案。如果 nginx 能给我一个 in-built variable $path_info,那会省事很多。

当然我并不想责怪 nginx(apache 已经够令人称赞了,nginx 能超越它实属不易),说不定 nginx 不搞 path_info 有它的道理。 既然这样,php 编程时就应该考虑到 path_info 是不可靠的。以前我不太注意环境参数的获得性,如今审视了一下,除了 $_SERVER[‘PATH_INFO’]以外,$_SERVER[’PHP_SELF’] 和 $_SERVER[’SCRIPT_NAME’] 也是不可靠的。

$_SERVER[‘REQUEST_URI’] 和 $_SERVER[’SCRIPT_FILENAME’] 相对可靠。

I can’t find a way to assign two handlers to a script

这可能是一个不可能实现的任务。

Actinic 的动态部分是 perl 写的,我不想触及 perl,陆陆续续做了很多 extensions,都是 php 写的。Actinic perl 有很多漏洞,其中一个就是会把我的 php 源码暴露。可能 Actinic 的目标用户都是 htmler,根本没指望 phper 会给它写 extensions(我也是没办法,早就不想干了)。我能做的,就是尽可能地调整 php 的代码方式,做到即使源码暴露了,也不包含机要信息。

我也不打算在 Actinic 浪费时间去研究怎么修改漏洞,只是最近把 apache module 研究了一遍,突然想重提这个历史遗留问题是否可以从 apache 方面给予弥补。漏洞的源头是 Actinic perl 读入我的 php script,当成 html 直接输出。如果能把 handler 再交给 php-cgi,那么 php tag 之间的内容就可以得到解析。

可是 apache 是以后缀推定 handler 的,那么某个后缀怎么可以有两个 cgi handlers?

So many bugs in ispconfig

ispconfig 可以做 hosting account management,但给我的感觉是打磨得不够精致。如果把它应用到生产环境,如果不怕客户抱怨,如果喜欢捉虫游戏,那倒可以用 ispconfig,毕竟免费的嘛,我看功能跟 isp 自己开发的 control panel 差不多(如 godaddy 的 turbo panel)。我感觉 ispconfig 还无法跟 cPanel, plesk 相抗衡,用在开发环境还凑合。

凭我的肉眼,我就发现了以下几个说大不大,说小不小的毛病。

按 ispconfig installation on Fedora documentation 一步步安装完毕以后,其实并没有安装 mod_fastcgi 或 mod_fcgid (暂且算是 documentation 失误吧),但控制面板里已经可以选择 php 模式为 fastcgi。我只好自己摸索着装 mod_fcgid。

但未装 mod_fcgid 倒没有问题,装好 mod_fcgid 重启 apache 反而会出错。因为 ispconfig.conf 里预设的 <IfModule mod_fcgid.c> 生效了,而里面一条 FCGIWrapper /var/www/php-fcgi-scripts/ispconfig/.php-fcgi-starter .php 会出错。原因是 /var/www/php-fcgi-scripts/ispconfig/.php-fcgi-starter 不存在,又得自己动手制作一个。

另外一个问题是,新建第二个站点时,ispconfig 不会自动添加 namevirtualhost directive,导致 apache 启动时会有一个 warn message,但不致命。

综 上,ispconfig 对 conf 文件生成和管理得不是很好,而 ispconfig 本身也是基于 apache 的 conf 来配置的,所以 ispconfig 一不小心就搞砸了,连 apache 都启动不起来,连更正的机会都没有了。用着 ispconfig,还得时刻准备着 ssh 手动去更正搞砸了的 conf 文件。