Linux 下 Nginx + PHP 环境的配置

从我开始折腾 WordPress、Typecho 博客至今,我折腾了无数次 Nginx 的安装、配置与 PHP 环境的搭建,看过各种各样的教程,它们往往都有一个共同点,就是仅仅是给你一些现成的命令复制粘贴,它们大多从操作的角度出发,并没有太多原理上的阐述。就像之前我看到 火丁笔记博客的一篇文章 所说:“如果大家不求甚解,一味的拷贝粘贴,早晚有一天会为此付出代价。”所以我希望通过这篇文章,能够在一个不一样的角度去描述这个过程,希望能对看到这篇文章的你有所帮助。

本文假定你对 Linux 的命令、程序的文件IO、HTTP 协议、PHP 有一些大致的了解。

服务器后端做的事情

首先我们应该对网站的结构有一个基本的认识,通常来说一个网站的分为前端与后端两部分。它们相互依存,前端在用户的浏览器中,负责内容的显示和交互。后端负责处理浏览器发来的请求,根据不同的请求生成对应的响应返回给前端展示给用户。

浏览器与服务器之间需要通信,必然得遵循一些标准,一个必备的标准就是 HTTP 协议啦。(可能有的网站会用WebSocket等协议,但这篇文章只讨论HTTP的内容)

从本质的角度来看,后端的主要工作就是处理浏览器发来的 HTTP 请求,并根据服务器程序的业务逻辑返回这些请求对应的 HTTP 响应给浏览器。后端返回的响应,可以是一段 HTML/CSS/JavaScript 代码、也可以是一张图片、一段音频、或者是一个个不同类型文件。

我们可以这么说,无论这个后端是什么程序编写的,只要它能依照 HTTP 协议返回符合协议规定格式的响应就可以满足要求。因此,与前端只能用 HTML/CSS/JavaScript 三件套不同,后端的语言,平台的选择可以有很多,例如 PHP, Python, Ruby, JavaScript(Node.js), Java, Go, C++, C 等等。PHP 只是其中的一种选择,由于 PHP 的部署简单,性能也不错,所以也十分流行。

Web服务器

后端处理 HTTP 请求,主要还是通过 Web 服务器来实现,Web 服务器返回响应的过程主要有两条途径:

  1. 返回请求的对应的磁盘文件
  2. 把请求分发给其他的程序处理,并把该程序处理的结果返回。

如图:

未分类

其中,这里的请求分发的过程,也叫作反向代理(Reverse Proxy)。

一般来说用的比较多的 Web 服务器软件有 Nginx、Apache 和 Lighttpd,它们都是 C 语言编写的,其中因为 Nginx 的配置较为简单,高性能且资源占用较少,在业界备受欢迎,这里我也选择了 Nginx 作为网站的 Web 服务器。

PHP的执行机制

PHP脚本的执行

PHP的全称是,PHP: Hypertext Preprocessor,中文名为“超文本预处理器”。也就是说,PHP主要还是为了处理文本而产生的,这从它的代码中也有体现,我们来尝试一个简单的例子:

新建一个文本文件,命名为 temp.php ,里面输入以下内容:

这是php标签外的内容
---------华丽丽的分割线---------
<?php
$a = 2333 + 6666;
echo "这是PHP标签内部的内容,将会返回一个运算结果n";
echo "2333+6666=" . $a;
?> 
---------又是一条华丽丽的分割线---------
这是php标签外部的内容
---------下一条华丽丽的分割线---------
<?php
$b = 12345;
echo "变量$b的值为" . $b;
?> 
---------最后一条华丽丽的分割线---------
php标签外部的内容

把 temp.php 交给PHP解释器执行,这里我以Linux命令行为例,下面是这段脚本运行后的结果:

未分类

也就是说,PHP 解释器在处理文本的时候,把 之外的内容直接输出到了标准输出流中,当遇到 之间的代码时,它运行了代码,再把代码中 echo 语句的结果输出到标准输出流中。

我们可以通过重定向的操作,把 PHP 解释器的标准输出流重定向到别的地方,例如,我这里把它的输出结果重定向到与代码同目录的 result.txt 里面。

这时候我们试一下把 result.txt cat出来,发现里面的内容也和之前的输出一样。

未分类

ps: 如果你对数据流的概念不熟悉,可以参考《鸟哥的 Linux 私房菜》关于流的描述

通过Web服务器运行PHP脚本

我们知道,PHP 这门语言主要应用在 Web 的领域中,所以一般 PHP 文件都是通过 Web 服务器来触发运行的。

为了方便测试,我们可以用下面的命令可以直接在 temp.php 所在的文件夹下启动一个临时的 PHP cli 开发服务器,就不用配环境那么麻烦了:

php -S 0.0.0.0:8888

未分类

在浏览器访问 http://0.0.0.0:8888/temp.php 或者 http://127.0.0.1:8888/temp.php ,查看源代码,我们可以看到如图所示的结果:

未分类

我们可以发现,浏览器中看到的内容,和我们之前用 PHP 运行这个脚本生成的输出结果是一样的。

忽略细节的话,在某种意义上我们也许也可以这么说,PHP 服务器程序在收到浏览器发过来的请求之后,运行脚本,把脚本的标准输出流重定向到了浏览器中,就像之前把命令行运行的结果重定向到了 result.txt 一般。

相比通过文件存储的静态网页,类似PHP每次接到请求后通过解释器执行,执行的结果来返回数据的页面,因为数据会根据实际情况而变化,通常也被称为“动态网页”。

CGI协议

随着动态网页的流行,这种服务器接收请求,执行程序,把执行程序的输出返回给浏览器的过程也逐渐成型。规范化这个过程也变得有必要起来,由此,CGI协议便诞生了。

CGI (Common Gateway Interface),中文名是“通用网关接口”,它定义了Web服务器与处理请求的程序之间传输数据需要遵循的标准。

一般来说,程序运行时,它与外界交互的途径是标准输入(stdin)、标准输出(stdout)和环境变量,有的可能涉及到其它的文件IO,CGI协议定义了HTTP请求、HTTP响应与程序运行的环境变量、输入流、输出流的对应关系,从而实现了通过后端程序来处理HTTP请求的功能,这个程序也被称为CGI中的“网关”(Gateway)。关于CGI协议的具体规定,我们可以参阅 RFC3875 的文档描述。 https://tools.ietf.org/html/rfc3875

CGI协议规定了,每一个HTTP请求交给一个网关程序的进程来处理。这个HTTP请求的请求头(Header),QueryString,以及其它关于客户端的信息,作为网关程序运行的环境变量,这个HTTP中的请求体(Body),作为网关程序运行的标准输入(stdin);网关程序执行过程中的标准输出(stdout),则作为这个HTTP请求的响应数据返回给Web服务器。

参考 http://www.php-internals.com/book/?p=chapt02/02-02-03-fastcgi 的介绍,引用一下:

CGI 的运行原理

  1. 客户端访问某个 URL 地址之后,通过 GET/POST/PUT 等方式提交数据,并通过 HTTP 协议向 Web 服务器发出请求。

  2. 服务器端的 HTTP Daemon(守护进程)启动一个子进程。然后在子进程中,将 HTTP 请求里描述的信息通过标准输入 stdin 和环境变量传递给 URL 指定的 CGI 程序,并启动此应用程序进行处理,处理结果通过标准输出 stdout 返回给 HTTP Daemon 子进程。

  3. 再由 HTTP Daemon 子进程通过 HTTP 协议返回给客户端。

上面的这段话理解可能还是比较抽象,下面我们就通过一次 GET 请求为例进行详细说明。

未分类

如图所示,本次请求的流程如下:

  1. 客户端访问 http://127.0.0.1:9003/cgi-bin/user?id=1
  2. 127.0.0.1 上监听 9003 端口的守护进程接受到该请求
  3. 通过解析 HTTP 头信息,得知是 GET 请求,并且请求的是 /cgi-bin/ 目录下的 user 文件。
  4. 将 uri 里的 id=1 通过存入 QUERY_STRING 环境变量。
  5. Web 守护进程 fork 一个子进程,然后在子进程中执行 user 程序,通过环境变量获取到id。
  6. 执行完毕之后,将结果通过标准输出返回到子进程。
  7. 子进程将结果返回给客户端。

基于PHP语言的Web程序,它的工作机制也类似于CGI的模型,但根据实际的情况,PHP 的具体实现会有些不一样。在PHP中,CGI协议所用到的程序,是通过它自带的 php-cgi 来支持的,这个后文会继续介绍。

SAPI 抽象层

刚刚我们可以注意到,我们可以通过浏览器发送HTTP请求的方式执行PHP,也可以在命令行通过运行文件的方式执行PHP,每种运行方式看起来很不一样,但是实际上它们内部的工作流程是一样的。这得益于PHP代码中的SAPI中间层。

那么,什么是SAPI呢?

首先我们来看看PHP的架构图(图片来自鸟哥的博客 ps: PHP的鸟哥和写 Linux 私房菜的鸟哥不是同一个人哦)

未分类

从图片中可以看出,PHP内部从下到上分为4层:

  • 负责 PHP 的执行的 Zend 引擎
  • Extensions 扩展层,各种基础的库和扩展都在这一层实现
  • SAPI:Server Application Programming Interface,服务端应用编程接口,它通过一些钩子函数,定义 PHP 与外部应用的交互,通过它可以实现 PHP 与上层应用的隔离,我们可以基于SAPI编写不同的应用适应不同的环境。
  • Application 层,这里代表了PHP应用的部分,如命令行下的脚本的执行,web服务器的脚本的执行等等

在这篇文章中,我们的关注点主要是在 Application 层与 SAPI 层的部分,理解了这些的话具体的部署自然就是水到渠成了。

对PHP的架构有了一些印象之后,我们可以知道,SAPI是一种不同的应用与PHP内核的交互方式,上层的应用通过SAPI定义的接口把代码和执行需要的环境变量,输入输出等数据交给PHP内核解析。

下面是一些常见的 SAPI 的应用实现:

  • Shell CLI: 通过命令行执行 PHP 程序用到的 SAPI。
  • Apache 2.0 Handler: 通过 Apache 服务器的 mod_php 模块部署 PHP 服务的运行方式
  • PHP 自带的 CGI/FastCGI 接口: PHP 本身实现了一个名为 php-cgi 的程序,它有 CGI、FastCGI 两种工作模式,专门处理 CGI/FastCGI 的请求
  • PHP-FPM: 这是一个 PHP 专用的 fastcgi 管理器,克服了 php-cgi 本身的一些问题,并且附加了许多适合大流量高并发网站的功能

早期的 PHP 为了适配多种多样的Web服务器环境,内置了许许多多的 SAPI ,到PHP 7以后,只保留了一部分重要的 SAPI,其它的都已经移除,下面是 PHP 7 以后移除的SAPI列表:(来自菜鸟教程)

aolserver, apache, apache_hooks, apache2filter, caudium, continuity, isapi, milter, nsapi, phttpd, pi3web, roxen, thttpd, tux, webjames

通过PHP的 php_sapi_name() 函数 我们可以获得当前PHP运行所使用的sapi的名字,实际的 PHP 代码编写中,我们可以根据这个函数的值判断程序所处的运行环境。

由于 SAPI 的多样,所以就有了许多不同的 PHP 部署方式,下面我会介绍一些常见的部署方式。

通过加载 Module 方式部署 PHP

Web 服务器除了可以通过 CGI 执行动态脚本外,还可以通过加载模块的方式来运行动态脚本,例如 Apache 的环境中是通过 mod_php 模块来实现运行PHP的,它利用了 Apache 2.0 Handler 这个 SAPI 与 PHP 解释器内核通信。

通过 Apache + mod_php 来部署 PHP 具有开箱即用,稳定成熟的特点,同时也有一些缺点:

  1. Web 服务器与 PHP 解释器之间是耦合的,程序出问题的时候不好定位是 Apache 的问题还是 PHP 这一层的问题
  2. 由于PHP的执行用户是与 Apache 相同的,这某些情况下可能有安全隐患
  3. 这种方式对于高并发大流量的场景下的性能消耗较大

所以我个人不太推荐通过这种方式在实际生产环境中部署PHP,当然,本地的开发环境还是挺适合的,尤其是Windows环境下。

FastCGI

上文提到了 FastCGI,它究竟是何方神圣呢?

首先我们回顾一下刚刚提到的CGI,它每次接到请求的时候,都需要根据请求的信息设置好参数,创建一个新的进程处理这个请求,处理完毕后退出程序,又叫 fork-and-execute 模式。进程的创建和销毁是一个耗费资源的过程,当系统的并发量一大,基于CGI的程序就撑不住了。

顾名思义,FastCGI = Fast + CGI,它是一种为了提高 CGI 程序性能的协议,是早期的 CGI 协议的改进版本。

参考 http://www.php-internals.com/book/?p=chapt02/02-02-03-fastcgi 的介绍,引用如下:

FastCGI是Web服务器和处理程序之间通信的一种协议, 是CGI的一种改进方案,FastCGI像是一个常驻(long-lived)型的CGI, 它可以一直执行,在请求到达时不会花费时间去fork一个进程来处理(这是CGI最为人诟病的fork-and-execute模式)。 正是因为他只是一个通信协议,它还支持分布式的运算,所以 FastCGI 程序可以在网站服务器以外的主机上执行,并且可以接受来自其它网站服务器的请求。

FastCGI 是与语言无关的、可伸缩架构的 CGI 开放扩展,将 CGI 解释器进程保持在内存中,以此获得较高的性能。 CGI 程序反复加载是 CGI 性能低下的主要原因,如果 CGI 程序保持在内存中并接受 FastCGI 进程管理器调度, 则可以提供良好的性能、伸缩性、Fail-Over 特性等。

FastCGI 工作流程如下:

  1. FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程,并等待来自 Web Server 的连接。
  2. Web 服务器与 FastCGI 进程管理器进行 Socket 通信,通过 FastCGI 协议发送 CGI 环境变量和标准输入数据给 CGI 解释器进程。
  3. CGI 解释器进程完成处理后将标准输出和错误信息从同一连接返回 Web Server。
  4. CGI 解释器进程接着等待并处理来自 Web Server 的下一个连接。

未分类

FastCGI 与传统 CGI 模式的区别之一则是 Web 服务器不是直接执行 CGI 程序了,而是通过 Socket 与 FastCGI 响应器(FastCGI 进程管理器)进行交互,也正是由于 FastCGI 进程管理器是基于 Socket 通信的,所以也是分布式的,Web 服务器可以和 CGI 响应器服务器分开部署。Web 服务器需要将数据 CGI/1.1 的规范封装在遵循 FastCGI 协议包中发送给 FastCGI 响应器程序。

相比 CGI,这里又多了个需要我们考虑的程序 —— FastCGI 进程管理器。还有一个通信协议——FastCGI 协议。同时,HTTP 请求也不是 Web 服务器自己处理了,而是封装成 FastCGI 数据包发送到负责 FastCGI 的服务器。

PHP 本身实现了一个名为 php-cgi 的程序,它有 CGI、FastCGI 两种工作模式,专门处理 CGI/FastCGI 的请求。

PHP-FPM

刚刚我们有提到,PHP 可以通过内置的 php-cgi 程序的 FastCGI 模式实现 FastCGI 进程管理器的功能,解决了 CGI 的资源占用和并发的性能问题,同时也支持了分布式计算。然而,在需求日益增长的时候,php-cgi也暴露出了一些问题。最大的问题是,php-cgi 的配置不够人性化,主要体现在其修改 php.ini 后,不支持平滑重启,每次都要先停止服务再启动才能更新配置,这在某些场景下显然是很致命的。

为了解决上面的问题,一位名叫 Andrei Nigmatulin 的大神开发了 PHP-FPM,打破了这个尴尬的局面。

PHP-FPM 是一个为 PHP 量身打造的专用 FastCGI 进程管理器,它解决了上面的问题,并且在许多方面表现十分出色,因此在 PHP 5.3 版本以后,PHP-FPM 已经正式内置在 PHP 中了。官网关于 PHP-FPM 的介绍

综上,需要部署 PHP 环境的话,Apache/Nginx + PHP-FPM 是优于CGI 和 Module 加载的一个很好的选择,下面我就以 Nginx 为例,介绍一下 Nginx 和 PHP-FPM 的配置方法。

Nginx与PHP-FPM的配置

首先我们得装好 Nginx,PHP 和 PHP-FPM,具体安装过程可以参考其它的教程。

由于不同的发行版的安装后的文件路径不太一样,所以这里只会提到一些比较关键的配置部分。

我们需要明确 Nginx、PHP-FPM 各自的角色,Nginx 本身可以是一个提供静态文件分发的Web服务器、也可以是一个反向代理服务器,它的工作模式十分灵活,取决于我们怎么配置Nginx。在这里我的预期是,当 Nginx 收到请求以后,如果请求的是静态文件,那么将这个静态文件返回;如果它是一个要执行 PHP 程序的请求,Nginx 需要将其转发到 PHP-FPM 处理,PHP-FPM 收到请求以后,调用 PHP 内核执行 PHP 脚本,把脚本的输出返回给 Nginx,Nginx 再把响应通过 HTTP 响应的方式返回给用户。

上文我们提到了,FastCGI 的请求、响应是遵循 FastCGI 协议的,而 Web 服务器处理的是 HTTP 协议,所以 Nginx 与 PHP-FPM 一起工作时,就涉及到了 HTTP 协议的请求、响应与 FastCGI 协议的请求、响应的互相转换。这个转换的工作,是通过 Nginx 内置的 fastcgi 模块来实现的。所以,我们需要解决的问题是,如何配置 Nginx,调用 fastcgi 模块来让需要执行PHP的请求正确地转发到 PHP-FPM 中运行呢?

这里我们需要关注两个配置文件,一个是 Nginx 的 nginx.conf ,另一个是 PHP-FPM 的 php-fpm.conf

PHP-FPM 的配置文件

首先是 php-fpm.conf,这是 PHP-FPM 主要的配置文件,不同的安装方式的路径可能不一样,用 Ubuntu 16.04 的 apt 安装的 PHP-FPM,路径位于 /etc/php/7.0/fpm/php-fpm.conf ,如果是手动编译安装的话,假设 prefix 是 /usr/local/php,它一般会位于 /usr/local/php/etc/php-fpm.conf 。

php-fpm.conf 里面默认是 PHP-FPM 的基本配置,这里一般没有多少需要改动的地方,我们的目光放在最后一行的 include=xxxxx ,apt 安装的 PHP-FPM 默认是 include=/etc/php/7.0/fpm/pool.d/.conf ,手动编译安装的是 include=/usr/local/php/etc/php-fpm.d/.conf ,顾名思义,这是一个 include 操作,就是引入 pool.d 或者是 php-fpm.d 目录下所有的 .conf 文件的意思。这个目录下的文件主要的功能是定义了 php-fpm 监听端口等的信息。

定位到 pool.d (也可能是 php-fpm.d ) 目录下可以发现,它里面一般只有一个 www.conf,打开里面的内容,我们可以看到,里面每个配置项前面,都有一大堆详细的注释。这个文件是我们要配置 PHP-FPM 如何处理 PHP 的关键,它定义了 PHP-FPM 监听哪个端口或是 unix socket 的 FastCGI 请求,脚本执行环境的用户,用户组,权限等等。

一般如果对权限没有特殊要求的话我们不需要对它进行修改。这里我们需要记下里面 listen 选项的内容,listen 的值可以分为两种,一种是 TCP socket 地址,另一种是 unix socket 地址。如果我们这里看到的 listen 的值可能是 127.0.0.1:9000,它是 TCP socket,如果它是一个具体的文件路径类似 /run/php/php7.0-fpm.sock 的值,那么它是一个 unix socket,unix socket 是一种进程间的通信方式,它的操作类似网络套接字,但它实际的数据传输是不用经过网络层的,具体的内容可以查找相关的资料。

Nginx.conf 的配置

找到了 PHP-FPM 监听的 socket 之后,我们下一个目标就是配置 Nginx 让 .php 的请求转发到这个 socket 上了。

关于 Nginx 配置,推荐阅读官方文档 https://www.nginx.com/resources/admin-guide/nginx-web-server/#virtual-server 下面解释几个关键的部分

一般来说 Nginx 配置的基本结构是这样的,把 Nginx 用作 Web 服务器,则需要配置 nginx.conf 中的 http 块:

http {
    server {
        # Server configuration
        location / {

        }
        location ~ .php$ {

        }
    }
}

一个 http 块中可以有多个 server 块,每个 server 块代表了一个 Web 服务器,负责不同的域名、端口的请求的处理。在 server 块中,我们还可以配置不同的 location 块,每个 location 块都设置了它所匹配的 request URI 规则,符合规则的 request URI 将会跳到相应的 location 块中来处理。

location 语句块

利用 location 的强大功能,我们可以把所有 request URI 后缀为 .php 的请求交给一个 location 来处理,查询 Nginx 文档中关于 location 的描述,我们可以发现,location 的匹配 request URI 有两种方式:前缀匹配(prefix)和正则表达式匹配。.php 是后缀,所以需要正则表达式来处理,这里的正则表达式是 .php$ location 后的~ 代表匹配规则是一个正则表达式。

# 匹配 .php 后缀的 location
location ~ .php$ {

}

fastcgi 模块

启用 fastcgi 转发,需要用到 fastcgi 模块,在 location 块中配置与 fastcgi 相关的指令就可以了,官方文档对 fastcgi 模块的各个配置指令有很详细的介绍 http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

一般我们使用的时候,我们只需用到 fastcgi 模块的其中两条指令,分别是 fastcgi_pass, fastcgi_param.

  • fastcgi_pass 这个参数是最最关键的,设置请求通过 fastcgi 协议转发的地址,需要设置为我们刚刚找到的 PHP-FPM 监听的地址。具体用法参考官网文档
  • fastcgi_param 这条指令描述了请求的一些关键参数的设定,比如要执行的脚本文件路径,请求的 QueryString,请求方法(GET还是POST),请求是否为 HTTPS 等等。详情继续参考官网文档

fastcgi_params

fastcgi_param 一般的用法如下

fastcgi_param parameter value [if_not_empty];

parameter 为当前 fastcgi 协议请求的参数名,value 为 Nginx 要设定此参数的参数值。这个value可以是一个固定的值,也可以是一个变量。我们可以根据实际的需要,来设置不同的 paramter 的参数值 http://nginx.org/en/docs/varindex.html

parameter 中有一个参数是最关键的,它就是 SCRIPT_FILENAME,这个参数定义了这个请求让 PHP-FPM 运行的 php 文件的完整路径,如果没有它,PHP-FPM 就不知道该运行什么脚本,将会返回一个内容为空白的 200 响应(https://nginx.org/en/docs/beginners_guide.html#fastcgi)。(实测在 PHP 7 环境中缺少 REQUEST_METHOD 也会有这种情况)

这个最关键的参数一般是这么设置的,意思是把 SCRIPT_FILENAME 设置为之前用 root 或 alias 设定的路径 + 脚本的相对路径:

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

举个例子,假如当前的网站根路径设置为 /var/www/html,我们访问的 .php 文件的地址是 http://example.com/test/test.php ,那么,这时候的 $document_root 的值为 /var/www/html ,$fastcgi_script_name 的值就是 /test/test.php 了。此时的 SCRIPT_FILENAME 将会被设置为 /var/www/html/test/test.php ,PHP-FPM 就会按照这个路径读取 php 代码了。

特殊情况下,我们可以直接把一个确定的路径代替 $document_root:

fastcgi_param  SCRIPT_FILENAME    /var/www/html$fastcgi_script_name;

除了最关键的参数外还有一系列的参数需要设置,参考 Nginx 的文档 PHP FastCGI Example | NGINX

大部分时候,fastcgi_param 的设置是固定的,由于太多人瞎粘贴配置,各种混乱,所以 Nginx 后来为我们提供了一个现成的 fastcgi_param 配置的集合,位于 nginx.conf 同目录的 fastcgi_params 和 fastcgi.conf 中,fastcgi_params 的内容如下:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

所以,我们在配置 location 时,在 fastcgi_param 的设定上,我们设置完最关键的 SCRIPT_FILENAME 以后,只需要直接 include fastcgi_params; 就能完成任务了,下面是一个 location 配置 PHP 的例子。

location ~ .php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

这时候又衍生出了一个问题,fastcgi.conf 是做什么的呢?

fastcgi.conf 和 fastcgi_params 的内容相比,多了刚刚我们提到的最最关键的一行,其它完全一致。

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

因为我们平常配置 PHP 环境的时候,直接指出文件的根路径反而会造成许多麻烦,配置文件也不灵活,所以Nginx 为了缓解这种情况,引入了一个 fastcgi.conf 来作为最一般的配置。

有了 fastcgi.conf ,我们的配置又可以更简单一些了~

location ~ .php$ {
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi.conf;
}

关于 fastcgi_params 和 fastcgi.conf 的关系,可以参考这篇文章 FASTCGI_PARAMS VERSUS FASTCGI.CONF – NGINX CONFIG HISTORY

一些安全的因素

参考 https://huoding.com/2013/10/23/290 火丁笔记 的描述,我们还需要在 nginx 这一层判断一下访问的 PHP 文件是否存在,避免出现因为 php.ini 开启了 cgi.fix_pathinfo=1 导致任意后缀文件都能通过 PHP 解释器解析产生的可能引发的安全问题,虽然这个漏洞在高版本 PHP (>=5.3.9) 已经被补上,但是还是需要注意。这时候我们可以在负责 php 的 location 块增加一个 try_files 来解决。修改后的配置如下:

location ~ .php$ {
    try_files $uri =404;
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi.conf;
}

PATH_INFO 的配置

许多著名的 PHP 程序、框架如 WordPress、Typecho、Moodle、ThinkPHP 等,会支持形如 /xxxx.php/archives/2333 的 request URI,比如说,Moodle许多文件的路径就是 http://xxx.com/lib/javascript.php/1512059879/lib/requirejs/jquery-private.js 这种类型。这样的URL看起来比较神奇,仿佛 php 文件就是一个文件夹一样,看起来也更加友好一些。

一般 PHP 程序要处理这样的 request URI ,是通过超全局变量 $_SERVER 的一个参数 $_SERVER[‘PATH_INFO’] 来实现的,这是一个很实用的参数,PHP 文档对它的描述如下:

包含由客户端提供的、跟在真实脚本名称之后并且在查询语句(query string)之前的路径信息,如果存在的话。例如,如果当前脚本是通过 URL http://www.example.com/php/path_info.php/some/stuff?foo=bar 被访问,那么 $_SERVER[‘PATH_INFO’] 将包含 /some/stuff。

我们可以看见,PATH_INFO 的信息必须是“客户端提供的”,也就是说需要由 Web 服务器提供,并且它的内容是跟在脚本名称后,在查询语句(QueryString)前的路径信息。Nginx 默认不会提供 PHP_INFO,因此,如果需要这个功能,我们需要为 Nginx 的 fastcgi_param 设置关于 PATH_INFO 的信息。

首先第一步我们要知道,面对 /xxx.php/xxxx 这样的链接,其实 Nginx 会把它当做一个文件夹来解析,而我们之前的配置是必须保证 request URI 是 .php 结尾的,所以我们第一个目标,就是把 /xxx.php/xxxx 能交给处理 PHP 的 location 块。我们变通一下,增加一种匹配 xxx.php/ 的情况:

至于为什么 .php 后一定要有 / 或者是 $(代表文件结尾),主要是考虑到一种风险,有的网站会有上传功能,若用户上传了一个 xxx.php.jpg ,配置不当的时候可能导致这个 xxx.php.jpg 传入 PHP 解释器,产生挂马的可能性。

location ~ .php(/|$) {

}

这时候请求已经可以转到这个 location 来处理了,但是,这时候,对于 /xxx.php/xxx,Nginx 转发给 PHP-FPM 的 fastcgi 请求中,SCRIPT_FILENAME 就成了 /xxx.php/xxx 。PHP 实际执行脚本的路径就需要依靠 PHP 内部去解析了,而且 PHP 并不知道 PATH_INFO 是什么。所以我们需要进一步操作,把正确的 SCRIPT_FILENAME 和 PATH_INFO 交给 PHP ,这里就用到了 Nginx 的 fastcgi_split_path_info 了。

fastcgi_split_path_info 在 Nginx 官方文档的描述是这样的 : http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_split_path_info

Defines a regular expression that captures a value for the $fastcgi_path_info variable. The regular expression should have two captures: the first becomes a value of the $fastcgi_script_name variable, the second becomes a value of the $fastcgi_path_info variable. For example, with these settings
location ~ ^(.+.php)(.*)$ {
    fastcgi_split_path_info       ^(.+.php)(.*)$;
    fastcgi_param SCRIPT_FILENAME /path/to/php$fastcgi_script_name;
    fastcgi_param PATH_INFO       $fastcgi_path_info;

and the “/show.php/article/0001” request, the SCRIPT_FILENAME parameter will be equal to “/path/to/php/show.php”, and the PATH_INFO parameter will be equal to “/article/0001”.

使用 fastcgi_split_path_info ,需要设置一个含有两对括号正则表达式,第一个括号匹配的值将放入 $fastcgi_script_name 变量中,第二个括号的值是 $fastcgi_path_info 变量中,结合之前提到的 fastcgi_param 设置,SCRIPT_FILE_NAME 就已经正确设置啦,不用劳烦 PHP 去解析了,同时,我们再设置一个 fastcgi_param PATH_INFO 就能把 PATHINFO 设置好了。

这种情况的 location 配置如下:

location ~ .php(/|$) {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_split_path_info ^(.+?.php)(.*)$; #加入 ? 是为了避免类似 /test.php/t.php 匹配出错
    fastcgi_param PATH_INFO $fastcgi_path_info;
    include fastcgi.conf;
}

但这个配置还有一些问题,它没有考虑当访问请求的PHP不存在的时候的情况,访问文件不存在的 PHP 请求还是交给了 PHP-FPM 处理,仍然存在一些风险,一般是可以忽略不计的,但是为了完美还是要改进改进。

之前的配置我们用了 try_files $uri =404; 由于这时候的 request URI 并没有一个文件与之对应,所以使用 try_files $uri =404; 的话,肯定是直接返回 404 Not Found 。或许我们会想,request URI 没有对应的文件,那我们改成 try_files $fastcgi_script_name =404; 不就好啦!

这时候的配置类似这样,值得注意的是,Nginx 的配置并不会像一门编程语言一样有先后执行顺序,经过测试,fastcgi_split_path_info 有着更高的优先级,所以 try_files 不管加在哪一个位置,都能得到正确的结果,这里放在 fastcgi_split_path_info 后面是为了符合我们的直觉。

location ~ .php(/|$) {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_split_path_info ^(.+?.php)(.*)$;
    try_files $fastcgi_script_name =404;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    include fastcgi.conf;
}

但这样的配置又衍生出了新的问题,使用这个配置的时候,虽然 PHP 可以正常执行,但PHP脚本是获取不到 PATH_INFO 信息,这是为什么呢?

我们需要理解一下 try_files 指令的执行流程。根据 Nginx 关于 try_files 的文档 :

Checks the existence of files in the specified order and uses the first found file for request processing; the processing is performed in the current context. The path to a file is constructed from the *file* parameter according to the root and alias directives. It is possible to check directory’s existence by specifying a slash at the end of a name, e.g. “$uri/”. If none of the files were found, an internal redirect to the *uri* specified in the last parameter is made.

try_files 会依次检测传入的参数对应的文件路径是否存在对应的文件,若存在,则按参数所在 location 对应的 request URI 来处理,正如我标注的第一句加粗字体所述,try_files 的处理是取决于这个语句所在的上下文的,当这个 location 设置了 fastcgi_pass ,则这个请求会交给 fastcgi 模块发到后端 PHP-FPM 处理,如果这个 location 内部什么都没有,则会按照默认的文件读取返回来处理;如果前面所有的参数都不匹配的话,则会跳转到最后一个参数描述的内部重定向请求,跳到其他的 location 或返回状态码。

参考科大LUG老板踩过的 https://servers.ustclug.org/2014/09/nginx-try_files-fallacy/

回到刚刚的 try_files $fastcgi_script_name =404; 这里首先尝试的是 $fastcgi_script_name ,也就是说,跳到这一步以后,交回给这个 location 处理的 request URI 变成了 $fastcgi_script_name 这个变量所代表的,到了 .php 以后就结束。原本的 PATH_INFO 已经丢失了,这时候再 fastcgi_split_path_info 显然,$fastcgi_pathinfo 的值也变成了空白。

但是,我们第一步我们已经拿到了正确的 PATH_INFO 了,我们有没有什么办法可以把这个变量临时保存下来而不是在下一次匹配的时候就被覆盖掉呢?答案当然是有的~

这里就需要用到 Nginx 的 rewrite 模块的变量机制了,关于变量,我找到一篇讲解十分详细的文章 Nginx 变量漫谈(一)agentzh新浪博客http://blog.sina.com.cn/s/blog_6d579ff40100wi7p.html

这里用到的指令是 set ,变量需要注意的一点是,变量设置以后,变量名的可见范围是整个 Nginx 配置,在不同的请求中,变量值是独立的。所以,我们可以定义一个变量取名为 $real_path_info ,用来暂时存放这个值,增加了 set 指令以后的配置如下:

location ~ .php(/|$) {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_split_path_info ^(.+?.php)(.*)$;
    set $real_path_info $fastcgi_path_info;
    try_files $fastcgi_script_name =404;
    fastcgi_param PATH_INFO $real_path_info;
    include fastcgi.conf;
}

有些通用的程序为了兼容各种各样的服务器主机环境,它会使用多种不同手段来获取 .php 后面还存在的内容,所以它们也不一定用到 PATH_INFO 的方式,可能不需要配置得那么详细,但不代表我们不需要知道这些。只有在了解这些的情况下,遇到故障,我们也可以更容易更好地排查问题所在。

针对单入口程序的设置

许多 PHP 框架如 Laravel 采用了统一入口的方式,即它是通过 index.php 作为程序的入口,当我把 index 设置为 index.php 以后, /archives/2333 和 /index.php/archives/2333 就是等价的了,显然前者更加美观一些,这样还能实现“URI路由”操作,让不同 controller 来处理不同的 request URI,也就是我们常说的“伪静态”啦。

那么,“伪静态”该怎么实现呢,这里也用到了 Nginx 的 try_files 指令,方法是加一个 location / 的块:

location / {
  try_files $uri $uri/ /index.php$is_args$args
}
location ~ .php$ {
  fastcgi_pass 127.0.0.1:9000;
  include fastcgi.conf;
}

当访问的文件或目录不存在时,程序将重定向到 /index.php 处理,后面的 $is_args$args 是因为重定向以后 QueryString 丢失了,需要加回来。

我们可以发现,按照这样的配置重定向以后,request URI 变成了 /index.php?xxxx 了, 传给 PHP 的 PATHINFO 信息也丢失了 (https://trac.nginx.org/nginx/ticket/321) ,所以一般这种单入口的程序或框架会有许多判断,一般这些程序会通过 REQUEST_URI 参数来获得 index.php 后的路径信息,实现路由。

需要把路径信息传递到 PATH_INFO 的话,还有一种方案,就是通过 rewrite 来实现,假若文件不存在,则在 request URI 之前加上 /index.php 再继续解析。

参考 Typecho 的源码 (https://github.com/typecho/typecho/blob/master/tools/default_site) :

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php$1 last;
    }

    location ~ .php(/|$) {
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_split_path_info ^(.+?.php)(.*)$;
      fastcgi_param PATH_INFO $fastcgi_path_info;
      include fastcgi.conf;
    }

}

这时候也不需要 try_files 了。

可以复制粘贴的 server 配置总结

注意把 fastcgi_pass 设定为服务器中 PHP-FPM 监听的连接。

如果只是想单纯地配一个 .php 的解析

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_pass 127.0.0.1:9000;
        include fastcgi.conf;
    }

}

如果想让脚本支持 PATH_INFO

类似 Moodle 的文件下载那种

如果程序是通过 REQUEST_URI 来获取路径而不需要 PATH_INFO 的话:

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    location ~ .php(/|$) {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_split_path_info ^(.+?.php)(.*)$;
        try_files $fastcgi_script_name =404;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi.conf;
    }
}

如果一定要完整获取 PATH_INFO 可以用这个完美支持的版本:

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    location ~ .php(/|$) {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_split_path_info ^(.+?.php)(.*)$;
        set $real_path_info $fastcgi_path_info;
        try_files $fastcgi_script_name =404;
        fastcgi_param PATH_INFO $real_path_info;
        include fastcgi.conf;
    }
}

如果部署的是一个单入口程序,并且对 PATH_INFO 没有要求

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    location / {
          try_files $uri $uri/ /index.php$is_args$args
    }

    location ~ .php$ {
        fastcgi_pass 127.0.0.1:9000;
        include fastcgi.conf;
    }

}

如果部署的是一个单入口程序,需要支持 PATH_INFO 的话

server {
    listen 80 default_server;
    server_name _;

    root /www;
    index index.html index.php;

    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php$1 last;
    }

    location ~ .php(/|$) {
      fastcgi_pass 127.0.0.1:9000;
      fastcgi_split_path_info ^(.+?.php)(.*)$;
      fastcgi_param PATH_INFO $fastcgi_path_info;
      include fastcgi.conf;
    }

}

关于 Nginx 配置文件的内部执行机理,博主目前还不懂=_=,有些问题也难以解释,现在只能是从外部的表现,官网文档一点点推敲和纠正,待博主有时间有能力搞清楚它的时候,博主会不断地修正这里的描述的,也希望能与各位高手多多交流完善。

以上是博主的一些理解与实践的经验,由于博主的水平有限,可能有一些地方的描述不太妥当,若你发现了本文有不妥甚至错误之处,希望可以尽快在评论区中指出。要深入地理解 Nginx + PHP 配置,还得多参考一下官方的文档、源代码和一些高质量的博客文章。

Ubuntu 下为单版本和多版本 PHP 安装扩展

介绍

尽管 PHP 软件源提供了不少 PHP 扩展,但并不是提供所有的扩展,那么如果我们需要安装一个软件源没有提供的扩展应该怎么办呢?

利用 php-dev 就可以很方便的进行自行编译 PHP 扩展了。不过如果有使用 PHP 多版本共存就会更加麻烦一点。

单一PHP

一、安装 php-dev ,如果不是 7.1 需要自己修改一下版本号:

apt install php7.1-dev

二、以安装 Swoole 为例:

pecl install swoole

三、添加配置文件:

cd /etc/php/7.1/fpm/conf.d/
touch swoole.ini
echo "extension=swoole.so" | tee -a swoole.ini

四、重启 php-fpm 即可:

systemctl restart php7.1-fpm

多PHP共存

因为多个 PHP 就不能简单粗暴的使用 pecl 安装了,因为安装好了不知道是给谁用的。这里以 PHP7.1 和 PHP5.6 为例,还是 swoole。

一、安装 php-dev:

apt install php7.1-dev php5.6-dev

二、下载 swoole 源码 ,地址:http://pecl.php.net/package/swoole

cd /root/src
wget http://pecl.php.net/get/swoole-1.9.15.tgz
tar xzf swoole-1.9.15.tgz
cd swoole-1.9.15

三、为 PHP7.1 进行编译

cd /root/src/swoole-1.9.15
/usr/bin/phpize7.1
./configure --with-php-config=/usr/bin/php-config7.1
make && make install

四、为 PHP5.6 进行编译

cd /root/src/swoole-1.9.15
/usr/bin/phpize5.6
./configure --with-php-config=/usr/bin/php-config5.6
make && make install

五、添加 PHP7.1 配置文件

cd /etc/php/7.1/fpm/conf.d/
touch swoole.ini
echo "extension=swoole.so" | tee -a swoole.ini

六、添加 PHP5.6 配置文件

cd /etc/php/5.6/fpm/conf.d/
touch swoole.ini
echo "extension=swoole.so" | tee -a swoole.ini

七、重启 PHP-FPM

systemctl restart php7.1-fpm
systemctl restart php5.6-fpm

在 Ubuntu/Debian 下安装 PHP7.2

介绍

如果不出意外的 PHP 7.2.0 即将在 2017 年11月30日 发布 GA,届时大家就可以第一时间尝鲜了,所以这里先放出 PHP7.2 安装的教程以便大家升级。

适用系统: Ubuntu 16.04 LTS / Ubuntu 14.04 LTS / Debian 9 stretch / Debian 8 jessie

安装 PHP

Ondřej Surý 的 PHP PPA 为 Ubuntu 16.04/14.04 提供了 PHP7.2 版本,同时也有通过个人网站为 Debian 9/8 提供 PHP7.2 版本,因此 Ubuntu 是源于 Debian 所以基本可以通用,同时维护难度较低,软件源安装的 PHP 默认以 Unix Socket 的状态运行在 /run/php/php7.1-fpm.sock ,比使用 TCP 以 localhost:9000 的方式性能更好。

值得一提的是 Ondřej Surý 是 Debian PHP 软件源的官方维护者之一,所以说稳定性和安全性基本上不是问题。

由于 PHP7.2 是新出的版本势必有不少的兼容性问题,特别是国产的程序建议等待开发者通知再进行升级,一些 PECL 扩展可能也不会及时适配最新版。建议更新前提前做好备份准备。目前已知的是 WordPress 4.9 版本开始支持 PHP7.2。

添加软件源

Ubuntu

安装软件源拓展工具:

apt -y install software-properties-common

添加 Ondřej Surý 的 PHP PPA 源,需要按一次回车:

add-apt-repository ppa:ondrej/php  

更新软件源缓存:

apt update

Debian

添加 GPG

wget -O /etc/apt/trusted.gpg.d/php.gpg https://mirror.xtom.com.hk/sury/php/apt.gpg

安装 apt-transport-https

apt-get install apt-transport-https

添加 sury 软件源

sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'   

更新软件源缓存:

apt-get update

安装软件

安装 PHP7.2:

apt install php7.2-fpm php7.2-mysql php7.2-curl php7.2-gd php7.2-mbstring php7.2-xml php7.2-xmlrpc php7.2-zip php7.2-opcache -y

设置 PHP

安装完成后,编辑 /etc/php/7.2/fpm/php.ini 替换换 ;cgi.fix_pathinfo=1 为 cgi.fix_pathinfo=0 快捷命令:

sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/7.2/fpm/php.ini 

管理 PHP

安装好了先重启一下!

systemctl restart php7.2-fpm

更多操作:

systemctl restart php7.2-fpm #重启
systemctl start php7.2-fpm #启动
systemctl stop php7.2-fpm #关闭
systemctl status php7.2-fpm #检查状态

更新 PHP

运行下面的命令系统就会更新所有可以更新的软件包括 PHP

apt update
apt upgrade -y

安装更多组件

上面的一条命令安装 PHP 只是安装了部分 PHP 拓展,更多的软件可见:

[email protected]:~# apt-cache search php7.2

php-radius - radius client library for PHP
php-http - PECL HTTP module for PHP Extended HTTP Support
php-uploadprogress - file upload progress tracking extension for PHP
php-yaml - YAML-1.1 parser and emitter for PHP
php-mongodb - MongoDB driver for PHP
php-apcu - APC User Cache for PHP
php-imagick - Provides a wrapper to the ImageMagick library
php-ssh2 - Bindings for the libssh2 library
php-redis - PHP extension for interfacing with Redis
php-memcached - memcached extension module for PHP, uses libmemcached
php-apcu-bc - APCu Backwards Compatibility Module
php-rrd - PHP bindings to rrd tool system
php-uuid - PHP UUID extension
php-memcache - memcache extension module for PHP
php-zmq - ZeroMQ messaging bindings for PHP
php-igbinary - igbinary PHP serializer
php-msgpack - PHP extension for interfacing with MessagePack
php-geoip - GeoIP module for PHP
php-tideways - Tideways PHP Profiler Extension
php-yac - YAC (Yet Another Cache) for PHP
php-mailparse - Email message manipulation for PHP
php-oauth - OAuth 1.0 consumer and provider extension
php-gnupg - PHP wrapper around the gpgme library
php-propro - propro module for PHP
php-raphf - raphf module for PHP
php-solr - PHP extension for communicating with Apache Solr server
php-stomp - Streaming Text Oriented Messaging Protocol (STOMP) client module for PHP
php-gearman - PHP wrapper to libgearman
php-phalcon - full-stack PHP framework delivered as a C-extension
php-ds - PHP extension providing efficient data structures for PHP 7
php-sass - PHP bindings to libsass - fast, native Sass parsing in PHP
php-lua - PHP Embedded lua interpreter
libapache2-mod-php7.2 - server-side, HTML-embedded scripting language (Apache 2 module)
libphp7.2-embed - HTML-embedded scripting language (Embedded SAPI library)
php7.2-bcmath - Bcmath module for PHP
php7.2-bz2 - bzip2 module for PHP
php7.2-cgi - server-side, HTML-embedded scripting language (CGI binary)
php7.2-cli - command-line interpreter for the PHP scripting language
php7.2-common - documentation, examples and common module for PHP
php7.2-curl - CURL module for PHP
php7.2-dba - DBA module for PHP
php7.2-dev - Files for PHP7.2 module development
php7.2-enchant - Enchant module for PHP
php7.2-fpm - server-side, HTML-embedded scripting language (FPM-CGI binary)
php7.2-gd - GD module for PHP
php7.2-gmp - GMP module for PHP
php7.2-imap - IMAP module for PHP
php7.2-interbase - Interbase module for PHP
php7.2-intl - Internationalisation module for PHP
php7.2-json - JSON module for PHP
php7.2-ldap - LDAP module for PHP
php7.2-mbstring - MBSTRING module for PHP
php7.2-mysql - MySQL module for PHP
php7.2-odbc - ODBC module for PHP
php7.2-opcache - Zend OpCache module for PHP
php7.2-pgsql - PostgreSQL module for PHP
php7.2-phpdbg - server-side, HTML-embedded scripting language (PHPDBG binary)
php7.2-pspell - pspell module for PHP
php7.2-readline - readline module for PHP
php7.2-recode - recode module for PHP
php7.2-snmp - SNMP module for PHP
php7.2-soap - SOAP module for PHP
php7.2-sqlite3 - SQLite3 module for PHP
php7.2-sybase - Sybase module for PHP
php7.2-tidy - tidy module for PHP
php7.2-xml - DOM, SimpleXML, WDDX, XML, and XSL module for PHP
php7.2-xmlrpc - XMLRPC-EPI module for PHP
php7.2-zip - Zip module for PHP
php7.2-xsl - XSL module for PHP (dummy)
php7.2 - server-side, HTML-embedded scripting language (metapackage)
php7.2-sodium - libsodium module for PHP

apt方式安装LNMP环境教程(ubuntu17.10|PHP7.1)

1. 简要说明

安装环境是阿里云ubuntu17.10,这个教程里我把域名都写成hostname.com, ip都写成192.168.1.1,你可以根据自己的需要更换。另外如果不是root账户的话,最好切换到root账户。

sudo su

2. 安装MYSQL 5.7

我们使用apt-get方式安装MySQL:

apt-get -y install mysql-server mysql-client

安装的时候会要求你输入MySQL的root密码,建议此时输入,比较方便。
输入以下命令,让MySQL更安全:

mysql_secure_installation

我们将会被问这些问题:

root@root:~# mysql_secure_installationSecuring the MySQL server deployment.Enter password for user root: 这里输入MySQL密码VALIDATE PASSWORD PLUGIN can be used to test passwordsand improve security. It checks the strength of passwordand allows the users to set only those passwords which aresecure enough. Would you like to setup VALIDATE PASSWORD plugin?Press y|Y for Yes, any other key for No: 想要让MySQL验证密码强度可以填Y,但个人建议N。otherwise.Using existing password for root.Change the password for root ? ((Press y|Y for Yes, any other key for No) : 建议NoBy default, a MySQL installation has an anonymous user,allowing anyone to log into MySQL without having to havea user account created for them. This is intended only fortesting, and to make the installation go a bit smoother.You should remove them before moving into a productionenvironment.Remove anonymous users? (Press y|Y for Yes, any other key for No) : 这个要填YSuccess.Normally, root should only be allowed to connect from'localhost'. This ensures that someone cannot guess atthe root password from the network.Disallow root login remotely? (Press y|Y for Yes, any other key for No) : 是否允许root远程登录?想要更安全选Y,但我建议选N,可以避免很多问题。Success.By default, MySQL comes with a database named 'test' thatanyone can access. This is also intended only for testing,and should be removed before moving into a productionenvironment.Remove test database and access to it? (Press y|Y for Yes, any other key for No) : 这个选Y,移除没用的test表。- Dropping test database...Success.- Removing privileges on test database...Success.Reloading the privilege tables will ensure that all changesmade so far will take effect immediately.Reload privilege tables now? (Press y|Y for Yes, any other key for No) : 选YSuccess.All done!

MySQL就装好了。

3. 安装Nginx

apt-get install nginx
apt-get install nginx

然后你打开你的IP地址或域名,就能看见nginx默认页面了。

未分类

nginx默认的路径在/var/www/html

4. 安装PHP7.1

我们搭建wordpress博客主要要用到php-fpm组件,我们apt-get它,ubuntu会自动安装必要的php程序。

apt-get -y install php7.1-fpm

5. 配置nginx

编辑nginx的默认站点配置文件

vim /etc/nginx/sites-available/default
server { listen 80 default_server; listen [::]:80 default_server; # SSL configuration # # listen 443 ssl default_server; # listen [::]:443 ssl default_server; # # Note: You should disable gzip for SSL traffic. # See: https://bugs.debian.org/773332 # # Read up on ssl_ciphers to ensure a secure configuration. # See: https://bugs.debian.org/765782 # # Self signed certs generated by the ssl-cert package # Don't use them in a production server! # # include snippets/snakeoil.conf;root /var/www/html;# Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html;server_name _;location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; }# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ .php$ { include snippets/fastcgi-php.conf; # With php7.0-cgi alone: fastcgi_pass 127.0.0.1:9000; # With php7.0-fpm: # fastcgi_pass unix:/run/php/php7.0-fpm.sock; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # location ~ /.ht { deny all; }}

另外需要注意,我们是要安装wordpress的,所以又一个index的地方(# Add index.php to the list),在index后面要增加index.php

PHP-FPM默认是通过socket连接的,我们要改成用TCP链接。

vim /etc/php/7.0/fpm/pool.d/www.conf

修改listen:

;listen = /var/run/php5-fpm.sock
listen = 127.0.0.1:9000

像上面那样修改后,重启nginx:

service nginx restart

然后打开:

vim /etc/php/7.1/fpm/php.ini

设置cgi.fix_pathinfo=0

; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting; of zero causes PHP to behave as before. Default is 1. You should fix your scripts; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.; http://php.net/cgi.fix-pathinfocgi.fix_pathinfo=0

reload php-fpm:

service php7.0-fpm reload

新建一个文件:

vim /var/www/html/info.php

写入

<?php
phpinfo();
?>

然后我们可以打开浏览器输入域名,例如 192.168.1.1/info.php(你的显示应该是php7.1)

未分类

你可以看到PHP已经工作了,包括现在支持的一些模块。

6. 使PHP7.1支持MySQL

我们可以先看一下有哪些PHP7.1的模块:

apt-cache search php7.1

可以选一些你喜欢的模块安装(以下是我自己安装的):

apt-get -y install php7.1-mysql php7.1-gd php7.1-curl php7.1-intl php7.1-mcrypt

reload PHP-FPM:

service php7.1-fpm reload

刷新一下192.168.1.1/info.php,看一下自己的模块是否都安装好了。

这样就安装配置好了,接下来就可以在/var/www/html里上传wordpress文件了。

CentOS源码安装Apache、MySQL、PHP

一、下载Apache

1. 点击官网链接进入官网,按照下图下载源码包。

有很多下载方式,可以选择http也可以选择ftp,上面的gif我是用的是http方式,随便选择了一个镜像进入站点,然后选择了http目录,进入http目录后,选择了httpd-2.4.29.tar.gz文件,右键复制了该文件的链接地址。

在命令行输入下面的命令下载源码文件:

# wget http://apache.claz.org/httpd/httpd-2.4.29.tar.gz

2.下载后输入下面的命令解压:

# tar -zxvf httpd-2.4.29.tar.gz

二、下载依赖

1. 下载apr

点击链接官网地址下载,如下图

未分类

我选择的是apr-1.6.3.tar.gz的链接,依然是右键,然后选择复制链接地址,输入如下命令下载:

# wget http://apache.mirrors.lucidnetworks.net//apr/apr-1.6.3.tar.gz

输入如下命令解压:

# tar -zxvf apr-1.6.3.tar.gz

进入解压目录

# cd apr-1.6.3

输入如下命令安装

# ./configure --prefix=/wwwAdmin/apr-1.6.3
# make
# make install

--prefix=/wwwAdmin/apr-1.6.3是指定安装目录.

2. 下载apr-util

点击链接官网地址下载,如下图

未分类

我依然选择的是第一个链接,然后复制其地址,输入如下命令下载:

# wget http://mirror.stjschools.org/public/apache//apr/apr-util-1.6.1.tar.gz

然后输入如下命令解压:

# tar -zxvf apr-util-1.6.1.tar.gz

进入解压目录

# cd apr-util-1.6.1

输入如下命令安装

# ./configure --prefix=/wwwAdmin/apr-util-1.6.1 -with-apr=/wwwAdmin/apr-1.6.3/
# make
# make install

--prefix=/wwwAdmin/apr-util-1.6.1是指定安装目录,-with-apr=/wwwAdmin/apr-1.6.3/是apr的安装目录

3. 下载pcre

点击链接官网地址下载,选择一个版本(我选择的pcre2-10.30.tar.gz),然后右键复制其链接地址,输入如下命令下载:

# wget https://ftp.pcre.org/pub/pcre/pcre2-10.30.tar.gz

输入下面的命令解压:

# tar -zxvf pcre2-10.30.tar.gz

进入解压后的目录:

# cd pcre2-10.30

输入如下命令安装

# ./configure
# make
# make install</span>

使用默认安装位置/usr/local/pcre。

4. 下载OpenSSL

点击链接官网地址下载,我选择的是openssl-1.1.0g.tar.gz,然后右键复制其链接地址,输入如下命令下载:

# wget https://www.openssl.org/source/openssl-1.1.0g.tar.gz

注:get下载似乎是提示404,所以我直接点击下载到我的电脑上,然后用ftp工具上传到服务器目录下的。

输入下面的命令解压:

# tar -zxvf openssl-1.1.0g.tar.gz

进入解压后的目录:

# cd openssl-1.1.0g

输入如下命令安装

# ./config shared --prefix=/wwwAdmin/openssl1.1.0g --openssldir=/usr/local/ssl<span class="redactor-invisible-space">
# make
# make install
--prefix=/wwwAdmin/pcre2-10.30是指定安装目录

三、安装Apache

进入解压后的目录

# cd httpd-2.4.29

执行下面的命令安装

# ./configure --prefix=/wwwAdmin/apache2.4.29 --sysconfdir=/wwwAdmin/apache2.4.29/conf --enable-so --enable-ssl=/wwwAdmin/openssl1.1.0g --enable-cgi --enable-rewrite --with-zlib --with-pcre=/usr/local/pcre --with-apr=/wwwAdmin/apr-1.6.3 --with-apr-util=/wwwAdmin/apr-util-1.6.1 --enable-modules=most --enable-mpms-shared=all --with-mpm=event
# make
# make install

安装完成后进入安装目录/wwwAdmin/apache2.4.29下的conf目录,使用vi httpd.conf命令打开文件,找到

#ServerName www.example.com:80

修改为(去掉前面的#号,#号是注释)

ServerName 你的服务器ip:80

这样就可以启动服务器了,启动命令往下读就会看到。(修改ServerName)

注意:安装的时候如果报某个库没有安装是正常的,因为系统中可能默认安装了某些库,有的可能没有默认安装,如果用到了没有安装的库自然会报错。如果遇到困难可以留言给我。

四、安装MySQL

1. 下载MySQL

点击进入Community版下载地址,如下图所示

未分类

根据自己的需要选择版本和系统,然后复制下载链接地址,使用wget命令下载

# wget https://downloads.mysql.com/archives/get/file/mysql-5.5.47.tar.gz

2. 解压缩源码包

# tar -zxvf mysql-5.5.47.tar.gz

3. 源码安装CMake

安装MySQL需要CMake,所以我们先用源码安装CMake。

下载网址:https://cmake.org/download/

下载之后解压, 并进入解压后的目录,执行下面的命令安装:

# ./bootstrap 
# make 
# make install 

4. 源码安装

可以参考这张网页,也可以直接跟着我的描述往下操作。

安装Perl-GD库

# yum install perl-GD

创建用户和组

为了保证安全,我们为MySQL创建一个单独的用户和组。按照下面的操作创建(其中我的mysql要安装在/wwwAdmiin/mysql5.5.47目录下,数据库数据放置在/wwwAdmiin/mysql5.5.47/data目录下,所以下面的目录根据自己要安装的位置而定。)

# useradd -r -U mysql -M -d /wwwAdmiin/mysql5.5.47/data

安装Boost

下载Boost

下载地址:http://sourceforge.net/projects/boost/files/boost/…

下载成功后解压到/usr/local/boost目录下,其他目录也可以,这里是举个例子,下面会用到。Boost使用的是下载目录,而不是安装目录。

安装ncurses lib

网址:http://invisible-island.net/ncurses/ncurses.html#d…

下载地址:ftp://invisible-island.net/ncurses/ncurses.tar.gz

解压后进入解压后的目录依次执行下面的命令:

# ./configuration 
# make 
# make install 

安装MySQL

进入mysql源码包解压后的目录,依次执行下面的命令:

# cmake . -DCMAKE_INSTALL_PREFIX=/wwwAdmiin/mysql5.5.47 -DMYSQL_DATADIR=/wwwAdmiin/mysql5.5.47/data -DMYSQL_USER=mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_BOOST=/usr/local/boost 
# make 
# make install 

-DMYSQL_USER使用我们上面创建的用户mysql,-DWITH_BOOST指定的是boost的解压目录。

启动MySQL

进入MySQL安装目录的scripts目录下,执行下面的命令:

# ./mysql_install_db --user=mysql --basedir=/wwwAdmiin/mysql5.5.47 --datadir=/wwwAdmiin/mysql5.5.47/data

–user指定用户。如果报错:没有mysql_install_db的权限,使用chmod 777 mysql_install_db即可。

这行命令执行完后,命令行会给出提示接下来如何配置root密码,这个我忘记了。可以按照给出的提示修改,如果不敢操作,可以按照我下面介绍的方法修改,结果是一样的。

进入MySQL安装目录的bin目录下,依次执行下面的命令:

# ./mysqld_safe --user=mysql &
# ./mysql -u root -p //输入这行命令后提示输入密码时直接回车,因为默认密码是空的
# use mysql;
# UPDATE user SET Password = PASSWORD('更改后的密码') WHERE user = 'root';<span class="redactor-invisible-space">
# FLUSH PRIVILEGES;<span class="redactor-invisible-space"></span></span>

将MySQL启动服务添加到系统服务

进入MySQL安装目录,执行下面的命令:

# cp support-files/mysql.server /etc/init.d/mysql

五、安装PHP

1. 下载

下载地址:http://php.net/

2. 依赖库安装

Python(安装libxml2需要用到)

# yum search python | grep python-devel sudo yum install python-devel.x86_64 

libxml2

下载地址:ftp://xmlsoft.org/libxml2/

进入解压后的根目录,依次执行下面的命令:

# ./configuration 
# make 
# make install

libjpeg

# yum install libjpeg-devel

freetype

# yum install freetype-devel 

libmcrypt

下载地址ftp://mcrypt.hellug.gr/pub/crypto/mcrypt/libmcrypt

进入解压后的根目录,依次执行下面的命令:

# ./configuration 
# make
# make install 

libpng

下载地址:http://www.libpng.org/pub/png/libpng.html

下载完成后解压,然后进入解压目录,执行下面的命令:

# ./configuration
# make
# make install

3. 配置

# ./configure --prefix=/wwwAdmin/php5.5.38 --with-config-file-path=/wwwAdmin/php5.5.38/etc --with-apxs2=/wwwAdmin/apache2.4.29/bin/apxs --with-mysql=/wwwAdmin/mysql5.5.47/ --with-mysqli=/wwwAdmin/mysql5.5.47/bin/mysql_config --with-pcre-dir=/wwwAdmin/pcre2-10.30/ --with-libxml-dir --with-png-dir --with-jpeg-dir --with-fretype-dir --with-gd --with-zlib-dir --with-mcrypt --with-curl --enable-zip --enable-soap --enable-mbstring=all --enable-sockets --enable-calendar

4. 安装

# make
# make test
# make install

注意:以上安装并没有将各个服务设为开机启动,如果服务器发生reboot,需要手动开启Apache和MySQL。

apache的启动方式是进入安装目录下的bin目录,然后执行

# ./apachectl start

重启命令

# ./apachectl restart

停止命令

# ./apachectl stop

MySQL的启动相关命令

# service mysql start
# service mysql restart
# service mysql stop

喘口气~

到这里LAMP环境就安装完成了,下面进行配置

1、修改apache的用户名和组

我们先使用下面的命令新建用户:

# groupAdd wwwAdmin
# userAdd -g wwwAdmin wwwAdmin

然后进入apache安装目录下的conf目录下,使用 vi httpd.conf 命令打开apache的配置文件,找到

User daemon
Group daemon

修改为

User wwwAdmin
Group wwwAdmin

然后重启apache即可生效。

2、修改网站根目录

Apache默认的目录是在Apache安装目录下的htdocs目录下,我们可能希望将目录指向我们自建的位置,那么就需要做如下示例的配置:

  • 新建目录

我上面的安装目录都是wwwAdmin,我希望网站也放在这个目录中的某个文件夹下,进入wwwAdmin目录,执行下面的命令创建一个名为www的目录,用了存放网站。

mkdir www
  • 修改目录所属者和权限

我们将www目录的用户和组设置为Apache的用户和组,同时设置权限为可读写。

chown -r wwwAdmin:wwwAdmin www
chmod -r u+w www
  • 配置Apache网站根目录指向

打开apache的配置文件httpd.conf,找到

# symbolic links and aliases may be used to point to other locations.
#
DocumentRoot "/wwwAdmin/apache2.4.29/htdocs"
<Directory "/wwwAdmin/apache2.4.29/htdocs">
...
</Directory>

修改为

# symbolic links and aliases may be used to point to other locations.
#
DocumentRoot "/wwwAdmin/www"
<Directory "/wwwAdmin/www">
...
</Directory>

保存,然后重启apache即可生效。

3、配置Apache解析php

打开Apache的配置文件httpd.conf,找到

<Directory />
    AllowOverride none
    Require all denied
    ...
</Directory>

修改为(改为allow from all,不然会提示403)

<Directory />
    AllowOverride none
    #Require all denied
    Order deny,allow
    allow from all
</Directory>

找到

AddType application/x-gzip .gz .tgz

在下面添加(添加对php的解析)

AddType application/x-gzip .gz .tgz
AddType application/x-httpd-php .php 

找到

<IfModule dir_module>
    DirectoryIndex index.html index.htm index.php
</IfModule>

改为

<IfModule dir_module>
    DirectoryIndex index.html index.htm index.php
</IfModule>
  • 配置域名

打开Apache的配置文件httpd.conf,在文件末尾添加如下内容:

<VirtualHost *:80>
    ServerName www.geek-era.com
    DocumentRoot /wwwAdmin/www/basic/web
    <Directory "/wwwAdmin/www/basic/web">
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

DocumentRoot指定的是网站目录。

到这里整个环境的搭建就完成了(但是我的心情却有点复杂)

下面介绍几个可能会碰到的小问题

  • Fatal error: Can’t open and lock privilege tables: Table ‘mysql.user’ doesn’t exist

MySQL启动的时候使用的命令是

进入MySQL安装目录的scripts目录下,执行下面的命令:

# ./mysql_install_db --user=mysql --basedir=/wwwAdmiin/mysql5.5.47 --datadir=/wwwAdmiin/mysql5.5.47/data 

如果执行这行命令报如上的错误,则使用下面的命令:

# ./mysql_install_db --user=mysql --basedir=/wwwAdmiin/mysql5.5.47 --datadir=/wwwAdmiin/mysql5.5.47/data --ldata=/var/lib/mysql

我安装过程中确实碰到过这个问题,而且用这个方法解决了。

  • 安装PHP报错 ‘ext/date/lib/parse_date.lo’ is not a valid libtool object

出现这个问题是因为在源码包中已经运行过make,然后再次运行。解决方法很简单,执行 make clean 然后再执行 make 即可。

LINUX搭建LAMP(APACHE+PHP+MYSQL环境)CENTOS7.2版

我们更多的网站服务器是选择了Linux系统,这里建议你选择centos,这也是阿里云ecs推荐的系统,在服务器上比较推荐centos,特别对于新手,首选CentOS,并不是centos比Debian和ubuntu好,而是centos是初学者安装vps的首选,它既稳定,占用资源又少此版本生命周期较长,而且网上有关centos的教程很多,方便学习,我们这里选择了Centos7.2,采用yum在线安装。

一、 检查系统环境

1、确认centos版本

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)

2、检查是否安装过apache

rpm -qa | grep httpd

或者:

apachectl -v

或者:

httpd -v

3、检查是否安装过Mysql

service mysqld start

如果未被识别则没有安装
如果系统安装过,或者安装失败,清理一下系统

4、清理Mysql痕迹

yum remove mysql
rm -f /etc/my.cnf

5、卸载Apache包

rpm -qa|grep httpd

注意:如果是新的系统或者你从来没有尝试安装过,则以上步骤省略。

二、安装APACHE、PHP、MYSQL

1、安装apache

[root@localhost ~]# yum -y install httpd

直到返回

......
Installed:
  httpd.x86_64 0:2.4.6-40.el7.centos.4                                          

Dependency Installed:
  apr.x86_64 0:1.4.8-3.el7                      apr-util.x86_64 0:1.5.2-6.el7   
  httpd-tools.x86_64 0:2.4.6-40.el7.centos.4    mailcap.noarch 0:2.1.41-2.el7   

Complete!

表示安装成功!

2、安装Php

[root@localhost ~]# yum -y install php

直到返回:

......
Installed:
  php.x86_64 0:5.4.16-36.3.el7_2                                                

Dependency Installed:
  libzip.x86_64 0:0.10.1-8.el7             php-cli.x86_64 0:5.4.16-36.3.el7_2   
  php-common.x86_64 0:5.4.16-36.3.el7_2   

Complete!

3、安装php-fpm

[root@localhost ~]# yum -y install php-fpm

直到返回:

Installed:
  php-fpm.x86_64 0:5.4.16-36.3.el7_2                                            

Complete!

4、安装Mysql

[root@localhost ~]# yum -y install mysql

直到返回:

Installed:
  mariadb.x86_64 1:5.5.50-1.el7_2                                               

Dependency Updated:
  mariadb-libs.x86_64 1:5.5.50-1.el7_2                                          

Complete!

7.2版本的Centos已经把mysql更名为mariadb,表示安装成功!

5、安装 mysql-server

[root@localhost ~]# yum -y install mysql-server
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirror.lzu.edu.cn
 * extras: mirrors.nwsuaf.edu.cn
 * updates: mirrors.tuna.tsinghua.edu.cn
No package mysql-server available.
Error: Nothing to do

返回错误!!!

分析解决方案

CentOS 7+ 版本将MySQL数据库软件从默认的程序列表中移除,用mariadb代替了,entos7配置教程上,大多都是安装mariadb,因为centos7默认将mariadb视作mysql。

因为mysql被oracle收购后,原作者担心mysql闭源,所以又写了一个mariadb,这个数据库可以理解为mysql的分支。如果需要安装mariadb,只需通过yum就可。

解决方案:

二是从官网下载mysql-server

wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm

rpm -ivh mysql-community-release-el7-5.noarch.rpm

yum install mysql-community-server

然后需要确定,输入y回车即可

Install  3 Packages (+8 Dependent packages)

Total download size: 82 M
Is this ok [y/d/N]:

一直选择输入 y ,有两次选择,直到返回:

Replaced:
  mariadb.x86_64 1:5.5.50-1.el7_2      mariadb-libs.x86_64 1:5.5.50-1.el7_2     

Complete!

安装成功!!!

6、安装 php-mysql

[root@localhost ~]# yum -y install php-mysql

直到返回:

Installed:
  php-mysql.x86_64 0:5.4.16-36.3.el7_2                                          

Dependency Installed:
  php-pdo.x86_64 0:5.4.16-36.3.el7_2                                            

Complete!

安装成功!!!

三、安装基本常用扩展包

1、安装Apache扩展包

yum -y install httpd-manual mod_ssl mod_perl mod_auth_mysql

返回

......
Installed:
  httpd-manual.noarch 0:2.4.6-40.el7.centos.4                                   
  mod_ssl.x86_64 1:2.4.6-40.el7.centos.4                                        

Complete!

安装成功!!!

2、安装PHP扩展包

yum -y install php-gd php-xml php-mbstring php-ldap php-pear php-xmlrpc php-devel

返回:

......
Dependency Updated:
  pcre.x86_64 0:8.32-15.el7_2.1                                                 

Complete!

安装成功!!!

3、安装Mysql扩展包

yum -y install mysql-connector-odbc mysql-devel libdbi-dbd-mysql

返回:

......
Dependency Installed:
  libdbi.x86_64 0:0.8.4-6.el7         libdbi-drivers.x86_64 0:0.8.3-16.el7     
  unixODBC.x86_64 0:2.3.1-11.el7     

Complete!

安装成功!!!

四、配置APACHE、MYSQL开机启动

重启Apache、mysql服务(注意这里和centos6有区别,Cenots7+不能使用6的方式)

systemctl start httpd.service #启动apache
systemctl stop httpd.service #停止apache
systemctl restart httpd.service #重启apache
systemctl enable httpd.service #设置apache开机启动

启对应服务

service mysqld restart

service php-fpm start

service httpd restart

五、配置MYSQL

初次安装mysql是没有密码的,我们要设置密码,mysql的默认账户为root

设置 MySQL 数据 root 账户的密码:

[root@localhost ~]# mysql_secure_installation

当出现如下提示时候直接按回车:

Enter current password for root

出现如下再次回车:

Set root password? [Y/n]

出现如下提示输入你需要设置的密码,这里输入了root,输入密码是不显示的,回车后再输入一次确认:

New password:

接下来还会有四个确认,分别是:

Remove anonymous users? [Y/n]
Disallow root login remotely? [Y/n]
Remove test database and access to it? [Y/n]
Reload privilege tables now? [Y/n]

直接回车即可。

六、测试环境

1、我们在浏览器地址栏输入http://localhost/如下图,说明我们的apache测试成功

未分类

2、测试Php

进入apache的web根目录:/var/www/html 中写一个最简单的php测试页面

cd /var/www/html

touch phpinfo-test.php

vi phpinfo-test.php

进入到了控制模式之后按键盘字母 i 进入到编辑模式,将如下代码输入到文件中

<?php

echo "<title>Phpinfo Test.php</title>";

phpinfo()

?>

按 esc 退出编辑模式,回到控制模式,输入 :wq 然后回车,在浏览器中输入服地址http://localhost/phpinfo-test.php

出现下图则成功。

未分类

七、小结

1、我们采用了yum在线安装,版本都是默认的Php是5.4,apache的版本是Server version: Apache/2.4.6 (CentOS)

2、要想安装更高版本的php 阅读:centos7.2yum安装php70w.x86_64

3、以上教程亲测完成成功,极力推荐,如果你在调试过程中出现问题,留言讨论,如有错误,敬请指教。

php+crontab+shell方案实现的秒级定时发起异步请求回调方案

方案介绍

该方案出来的场景:一天有一个业务需求,需要把我方的一些信息或订单状态等异步发起请求同步给第三方,这里就会出现定时时间和延迟时间消息的处理,考虑过很多消息队列方案(如:rabbitmq、云消息服务等)。

不过最后公司定了因为该业务流量很小,不用做那么麻烦。所以就直接出了这个方案

该方案在50条消息/s,应该压力不大,量大了就会出现一个消息延迟问题,如果不注重这个业务时间准确性,该方案承载的秒级处理在1000内应该问题也不大

方案架构图

未分类

mysql的任务队列表

CREATE TABLE `open_queue` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '任务类型,可用于后续读取不同任务失败次数区分',
  `order_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '业务绑定的订单id等,根据自己自行设计业务id',
  `event_identity` varchar(30) NOT NULL DEFAULT '' COMMENT '事件表示分类,可不用,同type',
  `data` text NOT NULL COMMENT '需要处理的数据,json格式化',
  `try` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '特定任务需要当时就处理的次数,而不是发起回调请求的任务',
  `again` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0不是重试记录 1重试记录,失败后发起任务未1,否者未0',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否被执行 1是 0否',
  `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '任务创建的时间戳',
  `at_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '任务时间的时间戳',
  `task_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '执行结果 -1重复消息系统主动取消 0未执行 1执行成功 2执行失败',
  `task_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '执行时间',
  PRIMARY KEY (`id`),
  KEY `IDX_EVENT` (`event_identity`),
  KEY `IDX_AT_TIME` (`at_time`) USING BTREE,
  KEY `IDX_ORDER` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

方案实现源码

下方代码都是伪代码,大家自行根据思路自己设计业务

redis同步到mysql

该步骤可以省略,因为我方当时redis是单机部署,也没做持久化,

$redisTypeLock; //lock类型
if (!IS_WIN) {
    $file = fopen($path . "/lock/{$redisTypeLock}.lock","w+");
    if (!flock($file, LOCK_EX | LOCK_NB)) {
        flock($file,LOCK_UN);
        fclose($file);
        exit;
    }
}

//成功获得锁 开始业务执行

$count = get_redis_lLen('msgevent:' . $redisType);
if (!$count) {
    //无需要入数据库订单队列,直接返回
    return false;
}

$successCount = 0;
for ($i = 0; $i < $loopLen; $i++) {
    $data = get_redis_lPop('msgevent:' . $redisType);
    //把redis数据格式化存入到数据库持久化
    $result = $this->saveToMysql($data);
    if (!$result) {
        //如果执行失败,重新推入redis队列
        get_redis_lPush('msgevent:' . $redisType, $data);
    }

    $successCount++;
}

if (!IS_WIN) {
    flock($file, LOCK_UN);
    fclose($file);
}

定时执行任务的的入口

这个入口是定时秒级处理脚本和处理小于当前时间的脚本调用的入口

public function task_run()
{
    $timeType = $_GET['tt'] ? : 'now';
    //获取当前秒需要处理的所有消息, 依次路由后执行
    //如果你redis无需存入mysql,你可以redis的list结构实现, 一次全部取出数据集合,并删除list
    //如果接下去业务里发现发起请求失败了,重新把任务分配给redis建立恢复list,list名为t+需要什么时间执行的时间戳
    $lists = $this->getTaskLists($timeType);
    foreach ($lists as $item) {
        $this->task('event:' . $item['event_type']);
        //$this->task('event:demo1');
        //$this->task('event:demo2');
        //$this->task('event:demo3');
    }
}

实际处理业务(发起通知请求)

# 模拟一个发起的请求事件demo
private function pushDemo1Event($info)
{
    $sendInfoData = $this->getSendInfoFromInfo($info);

    //发起请求,这里做的真正的发起给对方的请求,你可以根据$sendInfo拼接各种事件或消息分类给对方
    $isSuccess = $this->sendInfo($sendInfoData);

    $data = array(
        'status'        => 1,
        'task_status'   => $isSuccess ? 1 : 2,
        'task_time'     => NOW_TIME
    );

    $db->save($data);

    //如果执行失败,重新插入一条记录,并把at_time生成下次执行的时间戳,这样定时器根据at_time字段可以取出判断执行到当时的秒级
    if (!$isSuccess) {

        $tryCount = $db->where(...)->count();

        if ($tryCount >= 5) {
            return false;
        }

        $this->newTask($info, $tryCount, 30);
    }
}

private function newTask($info, $tryCount, $timeout = 30)
{
    $newTime = $this->nextTaskTime($info, $timeout);
    $data = array(
        ...
        'at_time' => newTime //执行的时间(时间戳)
    );
    $db->add($data);
}

定时消费方

$timeType = $_GET['time_type'] ? : 'now';
$lists = $this->getMsgAll($timeType);
if (!$lists) {
    return false;
}

foreach ($lists as $item) {
    if ($item['type'] == '1') {
        //根据业务生成我方真实订单
        call_user_func_array(array($this, "add_real_order"), array($item));
    }
    if (in_array($item['type'], array(2,3))) {
        $this->push('push:toengineer', $item);  //指派或取消工程师
    }
    if ($item['type'] == '5') {
        $this->push('push:edit', $item);  //编辑
    }
    if ($item['type'] == '6') {
        $this->push('push:cancel', $item);  //取消
    }
    if ($item['type'] == '7' || $item['type'] == '8' || $item['type'] == '9') {
        $this->push('push:status', $item);  //简单订单状态
    }
}

借助crontab+shell外力实现秒级执行

因为crontab是最小单位是分钟,所以需要借助shell脚本来实现秒级执行

读取redis任务队列到mysql存储

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    #执行php直接返回,不会阻塞 不要忘记最后面的 &
    $(/usr/bin/php index.php redis2mysql > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run > /dev/null 2>&1 &)
    sleep 1
done

exit 0

处理秒级小于当前时间sh脚本

#!/bin/bash
cd /web/project/src
for (( i = 1; i < 60; i = i + 1 ))
do
    $(/usr/bin/php index.php task_run/tt/lt > /dev/null 2>&1 &)
    sleep 1
done

exit 0

linux下的crontab

* * * * * sh /web/project/src/shell/repair_build_order.sh
* * * * * sh /web/project/src/shell/repair_sync_second.sh
* * * * * sh /web/project/src/shell/repair_sync_lt_time.sh

php模拟linux crontab实现定时计划任务的方法

本文分享一下php模拟linux crontab实现定时计划任务的方法。

PHP定时计划任务需要两个文件。

1、crontab.conf.php (配置文件)

<?php
//当为0时,关闭计划任务
return 1;

2、crontab.php (主体文件)

<?php
ignore_user_abort();//关掉浏览器,PHP脚本也可以继续执行.
set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去

$interval = 60 * 3;// 每隔3分钟运行
$i = 0;

do {
    $nowTime = date("Y-m-d H:m:s");
    $run = include 'crontab.conf.php';

    if (!$run) {
        file_put_contents("crontab_task.log", "==计划任务已结束 $nowTime==rn", FILE_APPEND);
        die('Job has ended.');
    }

    //此处放要执行的代码
    sleep($interval);// 等待3分钟
    $data = "$i>计划任务正在运行中...(运行状态:$run)  $nowTimern";
    file_put_contents("tasktest.txt", $data, FILE_APPEND);
    $i++;
} while (true);

主要改变 crontab.conf.php 中 return 0 即可实现控制这个计划任务的关闭。

注意:当php服务关掉之后该计划任务脚本也会停掉并且不会自动重启。

实用的升级安装方法php,Nginx,mysql,zabbix等

升级版本编译安装当然Ok,依赖问题处理起来非常之繁琐,现在直接通过apt源来升级版本非常简便,下面我们来拿php做个升级实验

1. 添加下面两行到/etc/apt/sources.list,并将jessie替换为自己所使用的版本名称:

deb http://mirrors.ustc.edu.cn/dotdeb jessie all
deb-src http://mirrors.ustc.edu.cn/dotdeb jessie all

2. 可选项:

如果你想在Debian Squeeze上安装PHP5.4的话,再添加下面这两行:

deb http://mirrors.ustc.edu.cn/dotdeb squeeze-php54 all
deb-src http://mirrors.ustc.edu.cn/dotdeb squeeze-php54 all

如果你想在Debian Wheezy上安装未启用Zend Thread Safety的PHP5.6的话,再添加下面这两行:

deb http://mirrors.ustc.edu.cn/dotdeb wheezy-php56 all
deb-src http://mirrors.ustc.edu.cn/dotdeb wheezy-php56 all

如果你想在Debian Wheezy上安装启用Zend Thread Safety的PHP5.6的话,再添加下面这两行:

deb http://mirrors.ustc.edu.cn/dotdeb wheezy-php56-zts all
deb-src http://mirrors.ustc.edu.cn/dotdeb wheezy-php56-zts all

如果你想在Debian Wheezy上安装PHP5.5的话,再添加下面这两行:

deb http://mirrors.ustc.edu.cn/dotdeb wheezy-php55 all
deb-src http://mirrors.ustc.edu.cn/dotdeb wheezy-php55 all

3. 然后导入合适的GnuPG key

wget https://www.dotdeb.org/dotdeb.gpg
cat dotdeb.gpg | sudo apt-key add -

4. 运行

apt-get update

升级php例子:

升级前

一系列操作

401 2017-10-21 09:25:26 echo “deb http://packages.dotdeb.org wheezy-php56 all” >> /etc/apt/sources.list.d/dotdeb.list
402 2017-10-21 09:25:32 echo “deb-src http://packages.dotdeb.org wheezy-php56 all” >> /etc/apt/sources.list.d/dotdeb.list
403 2017-10-21 09:25:39 wget http://www.dotdeb.org/dotdeb.gpg -O- | apt-key add –
405 2017-10-21 09:26:17 dpkg -l |grep php
413 2017-10-21 09:28:30 for pkg in `dpkg -l |grep php |awk ‘{print $2}’`;do dpkg -P $pkg; done
425 2017-10-21 09:29:28 aptitude update
426 2017-10-21 09:29:46 aptitude install php5-fpm php5-cgi php5-cli php5-curl php5-gd php5-mcrypt php5-mysql php5-memcache php5-xmlrpc php5-dev libapache2-mod-php5

然后看看升级后的版本

未分类

ok升级完成,类似nginx等都可以参考这个方法。

阿里云 debian 下 apt-get 搭建 nginx+php环境

1. 更新apt-get源

apt-get update

2. 安装Nginx

apt-get install nginx

nginx相关操作

service nginx start
service nginx restart
service nginx stop

3. 安装php

apt-get install php5-fpm php5-gd php5-mysql php5-memcache php5-curl

4. 配置Nginx让其支持php

cd /etc/nginx/conf.d #进入nginx虚拟站点配置目录
vi xxx.com.conf #创建域名配置文件

然后把下面的代码拷贝进去

server {
    listen 80;
    server_name phpmyadmin.xxx.com;
    root /home/wwwroot/phpMyadmin;
    index index.php;
    location / {
        try_files $uri $uri/ =404;
    }
    location ~ .php$ {
        include snippets/fastcgi-php.conf;
    #
    #   # With php5-cgi alone:
    #   fastcgi_pass 127.0.0.1:9000;
    #   # With php5-fpm:
        fastcgi_pass unix:/var/run/php5-fpm.sock;
    }
}

附上nginx方向代理到nodeJs的配置

server{
    listen 80;
    server_name open.xxx.com;
    access_log off;
    location / {
        #proxy_cache_key "$scheme://$host$request_uri";
        #proxy_cache cache_one;
        #proxy_cache_valid  200 304 3h;
        #proxy_cache_valid 301 3d;
        #proxy_cache_valid any 10s;
        proxy_set_header   X-Real-IP  $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        #proxy_set_header   Referer http://xxxx;
        proxy_set_header   Host $host;
        #proxy_hide_header Set-Cookie;
        proxy_pass http://xx.xx.xx.xx:8080;
    }
}