Ubuntu 22.04 安装 Apache mod_ftp 0.9.6 指南

在 Ubuntu 22.04 上从源码编译安装 Apache mod_ftp 0.9.6-beta,并以 httpd 模块方式托管 FTP 服务的完整实测指南。

Apache mod_ftpmod_ftpApacheFTPWeb 服务器安装部署Ubuntu 22.042026/10/15

Ubuntu 22.04 安装 Apache mod_ftp 0.9.6 指南

目标:在一台 Ubuntu 22.04 服务器上通过 apxs 将 Apache mod_ftp 0.9.6-beta 编译为 httpd 动态模块,完成 FTP 站点配置、systemd 服务托管与登录/上传/下载验证,全流程已在 Ubuntu 22.04 实测通过。

一、环境信息

项目内容
操作系统Ubuntu 22.04.5 LTS(内核 6.8.0-138-generic)
系统架构x86_64(amd64)
编译工具链gcc 11.4.0、GNU Make、libtool、apr-1.0 构建脚本
httpd 版本Apache/2.4.52(apt 发行包,Server built 2026-10-05)
mod_ftp 版本0.9.6-beta(源码包于 2009-10-08 发布,此后未再发版)
测试客户端curl 7.81.0、lftp 4.9.2、netcat-openbsd
源码目录/opt/httpd-ftp
模块安装位置/usr/lib/apache2/modules/mod_ftp.so
FTP 根目录/srv/ftpdocs
服务管理方式systemd(apache2.service)

说明:mod_ftp 是 Apache httpd 的 FTP 协议模块,由 Covalent 捐赠、Apache 基金会以独立项目维护。截至目前它仍处于 beta 阶段,最新版本 0.9.6-beta 发布于 2009 年 10 月,多年未发新版,API 停留在 httpd 2.2/2.4 早期水平。这正是本文花大量篇幅记录编译排障的原因:在较新的 httpd 头文件下它有多处不兼容,需要少量手工修补。生产环境请谨慎评估,如仅需 FTP 服务可优先考虑 vsftpd/proftpd;选 mod_ftp 的价值在于复用 httpd 的认证、访问控制、日志与 SSL 体系。

💡 技术拓展:FTP 协议每传输一个文件使用两条 TCP 连接——控制连接(默认 21 端口,传输 USER/PASS/RETR 等命令)与数据连接(传文件列表和文件内容)。数据连接的建立方式分为主动模式(PORT)与被动模式(PASV):主动模式由服务器从 20 端口反连客户端指定地址,对客户端防火墙友好、对服务器侧不利;被动模式由服务器开一个临时端口等待客户端连接,是当今主流(应对客户端普遍处于 NAT 之后的现实)。mod_ftp 两种模式均支持,本文实测同时覆盖:FTPActiveRange 控制主动模式服务端出站端口,FTPPASVrange 控制被动模式监听端口范围。本文两种模式均实测通过。

二、前置条件与软件包准备

2.1 配置系统软件源并更新索引

apt-get update

回显(输出节选):

Get:12 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [4429 kB]
Fetched 28.9 MB in 8s (3425 kB/s)
Reading package lists...

2.2 安装 apache2 与 apache2-dev

apache2-dev 提供 apxs2 编译接口与 httpd 头文件,是编译第三方模块的必备包:

apt-get install -y apache2 apache2-dev

关键回显:

Setting up apache2-dev (2.4.52-1ubuntu4.24) ...

验证:

apache2 -v
apxs2 -q TARGET

回显:

Server version: Apache/2.4.52 (Ubuntu)
Server built:   2026-10-05T17:25:19
apache2

Ubuntu 上 apxs2 是指向 apxs 的符号链接:

ls -la /usr/bin/apxs*

回显:

lrwxrwxrwx 1 root root     4 Oct  5 17:25 /usr/bin/apxs2 -> apxs
-rwxr-xr-x 1 root root 20093 Oct  5 17:25 /usr/bin/apxs

2.3 安装下载与测试工具

apt-get install -y wget curl ca-certificates lftp netcat-openbsd iproute2 vim-common

说明:lftp 与 netcat-openbsd 用于后续功能验证,iproute2 提供 ss 命令查看端口监听。

三、版本选型与源码下载

3.1 版本说明

mod_ftp 官方发布位于 https://dlcdn.apache.org/httpd/mod_ftp/ ,当前仅有 0.9.6-beta 一个版本可下载。选择它没有版本可选空间,重点在于选配套的 httpd 版本:Ubuntu 22.04 仓库的 httpd 2.4.52 属于较新分支,实测确认 mod_ftp 0.9.6-beta 源码需要打 3 处小补丁才能编译、运行(详见第五章排障记录)。若选择更老的 httpd(如 2.4.29 之前的版本)可能免修补,但不建议为此降级安全版本。

3.2 下载并校验源码包

cd /opt
wget https://dlcdn.apache.org/httpd/mod_ftp/mod_ftp-0.9.6-beta.tar.gz
echo "17a97a15d9c6518875e8075676904471  mod_ftp-0.9.6-beta.tar.gz" | md5sum -c
sha256sum mod_ftp-0.9.6-beta.tar.gz

回显:

-rw-r--r-- 1 root root 129647 Oct  8  2009 mod_ftp-0.9.6-beta.tar.gz
mod_ftp-0.9.6-beta.tar.gz: OK
0ade7a99b19f0a4df3e2816303a8a0b3114aea49d100a0ae641e7b948e22fa3b  mod_ftp-0.9.6-beta.tar.gz

说明:Apache 官方随包发布了 MD5 校验文件(17a97a15d9c6518875e8075676904471)与 GPG 签名,md5sum -c 校验通过;SHA256 摘要 0ade7a99...e22fa3b 为本机实测计算值,可作为固定基线留存比对。

3.3 解压源码

tar xzf mod_ftp-0.9.6-beta.tar.gz
mv mod_ftp-0.9.6 httpd-ftp
ls /opt/httpd-ftp

回显:

CHANGES-FTP
LICENSE-FTP
Makefile-ftp.win
Makefile.apxs
NOTICE-FTP
NWGNUftp-makefile
README-FTP
STATUS-FTP
build
configure.apxs
docs
include
mod_ftp.dsw
modules

注意:压缩包内顶层目录名为 mod_ftp-0.9.6(不带 -beta 后缀),本文统一重命名为 /opt/httpd-ftp。

四、编译安装(方案 A:apxs 动态模块,推荐主线)

4.1 运行 configure.apxs

按照源码 README-FTP 的说明,apxs 构建方式为:

cd /opt/httpd-ftp
APXS=/usr/bin/apxs ./configure.apxs

回显:

Detecting features

Finished, run 'make' to compile mod_ftp

Run 'make FTPPORT=8021 install' to install mod_ftp
(The default FTPPORT is 21 if not specified)

The manual pages ftp/index.html and mod/mod_ftp.html
will be installed to help get you started.

The conf/extra/ftpd.conf will be installed as an example
for you to work from.  In your configuration file,
  /etc/apache2/apache2.conf
uncomment the line '#Include conf/extra/ftpd.conf'
to activate this example mod_ftp configuration.

4.2 首次 make:踩坑一(ftp_config.h 残缺宏)

make

实测报错:

In file included from ftp_internal.h:30,
                 from mod_ftp.c:24:
ftp_config.h:4:9: error: macro names must be identifiers
    4 | #define  1
      |         ^
ftp_config.h:7:9: error: macro names must be identifiers
    7 | #define  1
      |         ^
make[1]: *** [/usr/share/apache2/build/rules.mk:212: mod_ftp.slo] Error 1

定位:查看 modules/ftp/ftp_config.h,内容全部变成了 #define 1(宏名丢失)。根因在 configure.apxs 第 115-118 行:特性探测生成的 sed 表达式经 echo 与 eval 两次传递时反斜杠被吞掉,#define \1 1 中的反向引用 \1 丢失,只剩 #define 1。这是脚本级 bug,在当前 shell 环境下必然复现。

解决:手工按 modules/ftp/ftp_config.h.in 模板补齐宏名(现代 Linux 全部满足这些特性):

cat > /opt/httpd-ftp/modules/ftp/ftp_config.h <<'EOF'
/* modules/ftp/ftp_config.h.in used only for apxs builds */

/* Define to 1 if you have the `arpa/ftp.h' header. */
#define HAVE_ARPA_FTP_H 1

/* Define to 1 if you have the `fchmod' function. */
#define HAVE_FCHMOD 1

/* Define to 1 if you have the `netinet/ip.h' header. */
#define HAVE_NETINET_IP_H 1

/* Define to 1 if you have the declaration of `SOL_IP'. */
#define HAVE_SOL_IP 1

/* Define to 1 if `msg_accrights' is member of `struct msghdr'. */
#undef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS

/* Define to 1 if `msg_control' is member of `struct msghdr'. */
#define HAVE_STRUCT_MSGHDR_MSG_CONTROL 1

/* Define to 1 if you have the `sys/stat.h' header. */
#define HAVE_SYS_STAT_H 1
EOF

逐项解说:HAVE_ARPA_FTP_H/HAVE_NETINET_IP_H/HAVE_SYS_STAT_H 表示对应头文件存在;HAVE_FCHMOD 表示 fchmod 函数可用;HAVE_SOL_IP 与 HAVE_STRUCT_MSGHDR_MSG_CONTROL 用于主动模式低端口数据连接的辅助处理;HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS 在现代 glibc 中已不存在,保持 #undef 即可。

4.3 第二次 make:踩坑二与三(httpd 2.4 API 改名)

重新 make 后出现两组 API 改名错误,实测报错:

ftp_commands.c:1928:36: error: 'conn_rec' has no member named 'remote_ip'; did you mean 'remote_host'?
 1928 |         if (strcasecmp(ip_addr, c->remote_ip) != 0) {
ftp_message.c:59:48: error: 'conn_rec' has no member named 'remote_addr'
   59 |                 apr_getnameinfo(&remote_host, c->remote_addr, 0);

定位:mod_ftp 0.9.6 编写时 httpd 2.4 尚未发布,连接结构体 conn_rec 在 2.4 中已把 remote_ip 更名为 client_ip、remote_addr 更名为 client_addr(Ubuntu 22.04 的 /usr/include/apache2/httpd.h 第 1170 行附近可见 char *client_ip; 与 apr_sockaddr_t *client_addr;)。全源码仅 4 个 .c 文件涉及,且全部是对 conn_rec 的成员访问(模块自身结构体中无同名成员,可放心整体替换)。

解决:

cd /opt/httpd-ftp/modules/ftp
sed -i 's/->remote_ip/->client_ip/g' ftp_commands.c ftp_data_connection.c ftp_limitlogin.c ftp_message.c
sed -i 's/c->remote_addr/c->client_addr/g' ftp_message.c
grep -rn "remote_ip\|c->remote_addr" *.c | wc -l

回显:

0

继续编译:

cd /opt/httpd-ftp
make

成功尾部回显:

libtool: link: x86_64-linux-gnu-gcc -shared  -fPIC -DPIC  .libs/mod_ftp.o ... -Wl,-soname -Wl,mod_ftp.so -o .libs/mod_ftp.so
libtool: link: ( cd ".libs" && rm -f "mod_ftp.la" && ln -s "../mod_ftp.la" "mod_ftp.la" )
make[1]: Leaving directory '/opt/httpd-ftp/modules/ftp'

4.4 安装并清理 apxs 注入的重复配置

make install

关键回显:

Installing configuration files
mkdir /etc/apache2/extra
Installing FTP documents
mkdir /usr/share/apache2/ftpdocs
Installing online manual
mkdir /usr/share/apache2/default-site/htdocs
Installing header files

验证模块文件:

ls -l /usr/lib/apache2/modules/mod_ftp.so

回显:

-rwxr-xr-x 1 root root 457960 Oct 14 17:01 /usr/lib/apache2/modules/mod_ftp.so

踩坑四:make install 内部调用 apxs -i -a,会把一行相对路径的 LoadModule 追加进 /etc/apache2/apache2.conf,实测内容为 LoadModule ftp_module usr/lib/apache2/modules/mod_ftp.so(缺前导斜杠),同时追加注释行 #Include etc/apache2/extra/ftpd.conf。再次执行 apachectl configtest 时会出现 module ftp_module is already loaded, skipping 告警。处理方式是删除这两行,改用 Debian 规范的模块启用方式:

sed -i '/LoadModule ftp_module/d; /Include etc\/apache2\/extra\/ftpd.conf/d' /etc/apache2/apache2.conf
echo "LoadModule ftp_module /usr/lib/apache2/modules/mod_ftp.so" > /etc/apache2/mods-available/ftp.load
a2enmod ftp

回显:

Enabling module ftp.
To activate the new configuration, you need to run:
  systemctl restart apache2

语法检查:

apache2ctl configtest

回显:

Syntax OK

五、方案 B:一键脚本(备用方案,整合全部修补)

将第四章的全部修补动作固化为脚本,适合批量装机。以下脚本在干净系统上完整实测通过:

cat > /opt/httpd-ftp/build-modftp.sh <<'EOF'
#!/bin/bash
set -e
test -d /opt/httpd-ftp || { echo "请先解压源码到 /opt/httpd-ftp"; exit 1; }
cd /opt/httpd-ftp

cat > modules/ftp/ftp_config.h <<'CFG'
/* modules/ftp/ftp_config.h.in used only for apxs builds */
#define HAVE_ARPA_FTP_H 1
#define HAVE_FCHMOD 1
#define HAVE_NETINET_IP_H 1
#define HAVE_SOL_IP 1
#undef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
#define HAVE_STRUCT_MSGHDR_MSG_CONTROL 1
#define HAVE_SYS_STAT_H 1
CFG

sed -i 's/->remote_ip/->client_ip/g' \
  modules/ftp/ftp_commands.c \
  modules/ftp/ftp_data_connection.c \
  modules/ftp/ftp_limitlogin.c \
  modules/ftp/ftp_message.c
sed -i 's/c->remote_addr/c->client_addr/g' modules/ftp/ftp_message.c

APXS=/usr/bin/apxs ./configure.apxs
make
make install

sed -i '/LoadModule ftp_module/d; /Include etc\/apache2\/extra\/ftpd.conf/d' /etc/apache2/apache2.conf
echo "LoadModule ftp_module /usr/lib/apache2/modules/mod_ftp.so" > /etc/apache2/mods-available/ftp.load
a2enmod ftp
apache2ctl configtest
EOF
chmod +x /opt/httpd-ftp/build-modftp.sh

解说:脚本依次完成特性宏固化、两处 API 改名修补、configure/make/install,以及 Debian 化的模块注册;set -e 保证任一步失败立即中止。重复执行安全:sed 幂等,apxs 重复 -a 注入的行会被随后的 sed 清掉。

六、部署 FTP 站点

6.1 准备 FTP 根目录与测试文件

mkdir -p /srv/ftpdocs
echo "hello mod_ftp" > /srv/ftpdocs/welcome.txt
head -c 1M /dev/urandom > /srv/ftpdocs/bigfile.bin
chown -R www-data:www-data /srv/ftpdocs
ls -l /srv/ftpdocs

回显:

-rw-r--r-- 1 root root 1048576 Oct 14 16:52 bigfile.bin
-rw-r--r-- 1 root root      14 Oct 14 16:52 welcome.txt

说明:文件属主最终应为 www-data(apache2 工作进程用户),否则登录认证虽通过、STOR 上传会因目录权限被拒(实测踩过,见 FAQ Q7)。chown 后再 ls -l 属主列即显示 www-data。

6.2 创建登录账号

mod_ftp 的用户认证完全复用 httpd 的认证体系,这里用 htpasswd 文件认证(示例账号 ftpuser / 密码 q1w2e3r4):

htpasswd -cb /etc/apache2/ftpd.passwd ftpuser q1w2e3r4

回显:

Adding password for user ftpuser

6.3 编写 ftpd.conf

cat > /etc/apache2/conf-available/ftpd.conf <<'EOF'
# mod_ftp FTP 服务配置(Ubuntu apache2 适配版)
LogFormat    "%u [%a] %r" command_log_fmt
LogFormat    "%{%b %e %H:%M:%S %Y}t %T %a %B %U %M %F %d %W %u %S %Z %Y" transfer_log_fmt

Listen 21 ftp
AcceptFilter ftp none

<VirtualHost _default_:21>
    ServerAdmin  admin@example.com
    DocumentRoot "/srv/ftpdocs"
    FTP On

    FTPBannerMessage "Welcome to Apache mod_ftp (Ubuntu 22.04)"
    FTPTimeoutLogin  120
    FTPTimeoutIdle   600
    FTPTimeoutData   300
    FTPMaxLoginAttempts 3
    FTPUmask         022
    FTPPASVrange     50000 50100

    <Directory "/srv/ftpdocs">
        AuthType Basic
        AuthName "FTP Authentication"
        AuthUserFile "/etc/apache2/ftpd.passwd"
        require valid-user
    </Directory>

    ErrorLog  /var/log/apache2/ftp_error.log
    CustomLog /var/log/apache2/ftp_command.log command_log_fmt
    CustomLog /var/log/apache2/ftp_transfer.log transfer_log_fmt
</VirtualHost>
EOF
a2enconf ftpd

逐项解说:两条 LogFormat 定义命令日志与传输日志的别名,供 CustomLog 引用(若缺失会在日志中写入字面量 command_log_fmt,实测踩过);Listen 21 ftp 让 httpd 在 21 端口监听并标记协议名 ftp,配合 AcceptFilter ftp none 关闭内核 accept 过滤以兼容 FTP 的交互式握手;虚拟主机块中 FTP On 是启用 FTP 协议的总开关;三个 FTPTimeout* 分别限定登录、空闲、数据传输超时(秒);FTPPASVrange 50000 50100 限定被动模式数据端口范围便于防火墙放行;Directory 块把 htpasswd 认证挂到 FTP 根目录,登录即验活;三条日志分列错误/命令/传输。

6.4 启动服务并确认监听

systemctl restart apache2
systemctl is-active apache2
ss -tlnp | grep -E ':21|:80'

回显:

active
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*    users:(("apache2",pid=5804,fd=3),("apache2",pid=5803,fd=3),("apache2",pid=5800,fd=3))
LISTEN 0      511          0.0.0.0:21        0.0.0.0:*    users:(("apache2",pid=5804,fd=4),("apache2",pid=5803,fd=4),("apache2",pid=5800,fd=4))

说明:同一套 apache2 进程同时服务 HTTP(80) 与 FTP(21),这正是 mod_ftp 的设计价值——FTP 与 HTTP 共享 worker、模块与配置体系。

七、功能验证

7.1 curl 验证列表、下载与上传

curl -s --connect-timeout 10 ftp://127.0.0.1/ --user ftpuser:q1w2e3r4

回显:

-rw-r--r--    1 root     root      1048576 Oct 14 16:52 bigfile.bin
-rw-r--r--    1 www-data www-data       20 Oct 14 17:02 up.txt
-rw-r--r--    1 www-data www-data       14 Oct 14 16:52 welcome.txt

下载并校验 1MB 二进制文件:

curl -s -o /tmp/down.txt ftp://127.0.0.1/bigfile.bin --user ftpuser:q1w2e3r4
md5sum /tmp/down.txt /srv/ftpdocs/bigfile.bin

回显:

d56574d3db27a2f448cdc78305522229  /tmp/down.txt
d56574d3db27a2f448cdc78305522229  /srv/ftpdocs/bigfile.bin

上传并校验:

echo test-upload-content > /tmp/up.txt
curl -s -T /tmp/up.txt ftp://127.0.0.1/up.txt --user ftpuser:q1w2e3r4
md5sum /tmp/up.txt /srv/ftpdocs/up.txt

回显:

295faa30d24dde02ec36b4333d7d20b0  /tmp/up.txt
295faa30d24dde02ec36b4333d7d20b0  /srv/ftpdocs/up.txt

7.2 lftp 被动模式与主动模式

被动模式(passive-mode on)完整会话:

echo "file from lftp" > /tmp/lftp-put.txt
lftp -u ftpuser,q1w2e3r4 127.0.0.1 -e "set ftp:passive-mode on; pwd; ls; put /tmp/lftp-put.txt; get welcome.txt -o /tmp/lftp-get.txt; ls; bye"

回显:

ftp://ftpuser:q1w2e3r4@127.0.0.1
-rw-r--r--    1 www-data www-data  1048576 Oct 14 16:52 bigfile.bin
-rw-r--r--    1 www-data www-data       20 Oct 14 17:02 up.txt
-rw-r--r--    1 www-data www-data       14 Oct 14 16:52 welcome.txt
-rw-r--r--    1 www-data www-data  1048576 Oct 14 16:52 bigfile.bin
-rw-r--r--    1 www-data www-data       24 Oct 14 17:02 lftp-put.txt
-rw-r--r--    1 www-data www-data       20 Oct 14 17:02 up.txt
-rw-r--r--    1 www-data www-data       14 Oct 14 16:52 welcome.txt

主动模式(passive-mode off):

lftp -u ftpuser,q1w2e3r4 127.0.0.1 -e "set ftp:passive-mode off; ls; bye"

回显(输出节选):

-rw-r--r--    1 www-data www-data  1048576 Oct 14 16:52 bigfile.bin
-rw-r--r--    1 www-data www-data       24 Oct 14 17:02 lftp-put.txt
-rw-r--r--    1 www-data www-data       20 Oct 14 17:02 up.txt
-rw-r--r--    1 www-data www-data       14 Oct 14 16:52 welcome.txt

7.3 原始协议交互验证

用 netcat 直连 21 端口观察控制连接的原始回显:

printf 'USER ftpuser\r\nPASS q1w2e3r4\r\nSYST\r\nFEAT\r\nPWD\r\nQUIT\r\n' | nc 127.0.0.1 21

回显:

220-Welcome to Apache mod_ftp (Ubuntu 22.04)
220 node1.modftp.example FTP Server (Apache/2.4.52 (Ubuntu)) ready.
331 Password required for ftpuser
230 User ftpuser logged in
215 UNIX Type: L8 System: "Apache/2.4.52 (Ubuntu)"
211-Extensions supported
 EPSV
 TVFS
 UTF8
 MDTM
 EPRT
 SIZE
 PBSZ
 PROT
 AUTH TLS
 REST STREAM
211 End
257 "/" is current directory
221 Goodbye.

逐行解说:220 双行横幅来自 FTPBannerMessage 与服务器标识;331/230 是登录询问与成功;215 SYST 显示系统类型;211-End 之间为 FEAT 扩展能力表(EPSV/EPRT 扩展被动与主动、SIZE/MDTM 文件属性、PBSZ/PROT/AUTH TLS 为 FTPS 能力);257 显示当前目录;221 退出。FEAT 中出现 UTF8 说明 UTF-8 特性已默认启用(可用 FTPOptions NoUTF8Feature 隐藏)。

八、方案对比

维度方案 A:apxs 动态模块(本文主线)方案 B:一键脚本(方案 A 固化)方案 C:叠加进 httpd 源码树静态编译
操作复杂度中(需手工打 3 处补丁)低(一条脚本完成)高(需自行维护 httpd 源码构建)
适用场景单机快速部署、学习研究批量装机、自动化流水线对模块加载/性能有极致要求的定制发行
升级 httpd 影响需重编 mod_ftp需重跑脚本需整体重编 httpd
模块形态mod_ftp.so 动态加载同左静态编入 httpd 二进制
与 apt httpd 兼容完全兼容(实测 2.4.52)完全兼容不兼容(替换发行版 httpd)
实测结论通过通过未采用(成本高收益低)

解说:mod_ftp 源码 README 提供了 in-tree 静态编译路径(复制源码进 httpd 树后 buildconf/configure --enable-ftp),但对 Ubuntu 发行版 httpd 用户并不现实;动态 DSO 是唯一务实路径,因此方案 C 仅在表中列出供参考。

九、常见问题(FAQ)

Q1:configure.apxs 通过了,make 报 “macro names must be identifiers” 怎么办? A:这是 configure.apxs 脚本自身的 bug,特性探测的 sed 表达式经 echo/eval 传递后反斜杠丢失,生成了 #define 1 这类残缺宏。按第四章 4.2 节手工重写 modules/ftp/ftp_config.h 的 7 个特性宏即可,或直接使用第五章脚本。

Q2:make 报 “'conn_rec' has no member named 'remote_ip'”? A:httpd 2.4 已把 conn_rec::remote_ip 改名为 client_ip(remote_addr 同步改为 client_addr)。执行 sed -i 's/->remote_ip/->client_ip/g' ftp_commands.c ftp_data_connection.c ftp_limitlogin.c ftp_message.c 与 sed -i 's/c->remote_addr/c->client_addr/g' ftp_message.c 后重新 make。

Q3:编译安装全部成功,但客户端登录后一执行 USER 就断线(curl 报 56 response reading failed)? A:这是本次实测最深的一个坑。mod_ftp 0.9.6 手工构造 request_rec 时未初始化新版本 httpd 引入的 trailers_in/trailers_out 字段,而 httpd 2.4.52 的 ap_set_sub_req_protocol 会无条件 apr_table_copy(r->trailers_in),空指针导致子进程段错误(内核日志可见 apache2[...]: segfault at 10 ... in libapr-1.so)。修补方法:在 modules/ftp/ftp_protocol.c 的 ftp_read_request() 中,r->notes = apr_table_make(r->pool, 5); 之后追加四行:

r->trailers_in = apr_table_make(r->pool, 5);
r->trailers_out = apr_table_make(r->pool, 5);
r->useragent_addr = c->client_addr;
r->useragent_ip = c->client_ip;

重新 make && make install 并重启 apache2。用 gdb 单进程复现(gdb --args /usr/sbin/apache2 -DFOREGROUND -X)可看到完整调用栈:apr_table_copy ← ap_set_sub_req_protocol ← ap_sub_req_method_uri ← ftp_run_handler(ftp_commands.c:156)。

Q4:apache2ctl configtest 提示 “module ftp_module is already loaded, skipping”? A:make install 时 apxs -a 已把 LoadModule 写进 apache2.conf,与 mods-enabled/ftp.load 重复。删除 apache2.conf 中由 apxs 注入的那行(注意它还是相对路径,即使不重复也无法加载),保留 mods-available/ftp.load 方式即可。

Q5:mod_ftp 现在还在维护吗?用在生产稳妥吗? A:必须如实说明:mod_ftp 仍为 beta,最新版 0.9.6-beta 发布于 2009 年 10 月(源码包时间戳 Oct 8 2009),多年未发新版。它可以工作(本文完整实测),但缺少持续的安全审计。生产环境若只是要 FTP,优先 vsftpd/proftpd;只有当业务强依赖 httpd 认证/日志/SSL 联动时才建议 mod_ftp,并自行跟踪 httpd 安全公告。

Q6:Listen 21 ftp 里的 “ftp” 协议名是什么意思? A:httpd 2.2 起支持带协议参数的 Listen,为该端口选择协议处理链。mod_ftp 注册了 ftp 协议名,httpd 才会把 21 端口的连接交给 FTP 处理器而非 HTTP。同时建议配 AcceptFilter ftp none,因为 FTP 控制连接由服务器先发横幅,内核 accept 过滤等待数据会卡住握手。

Q7:登录正常但上传(STOR)失败、目录里没有文件? A:检查 FTP 根目录属主。apache2 以 www-data 身份写文件,实测目录属主为 root 时 curl -T 静默失败。执行 chown -R www-data:www-data /srv/ftpdocs 后恢复正常(md5 校验一致)。若仍失败,看 /var/log/apache2/ftp_error.log 与系统权限位。

Q8:21 端口被占用或不想用默认端口怎么办? A:把 ftpd.conf 中 Listen 21 ftp 与 VirtualHost default:21 的端口同步改为目标端口即可,例如 2121。make install 时也可用 make FTPPORT=8021 install 让示例配置直接按指定端口生成。

Q9:被动模式数据端口如何规划防火墙? A:用 FTPPASVrange 指令限定范围(本文用 FTPPASVrange 50000 50100),防火墙放行 21 与 50000-50100 即可。不配置时 mod_ftp 使用默认被动端口策略,在云环境下容易出现数据连接被防火墙拦截、LIST 卡住的现象。

Q10:如何彻底卸载 mod_ftp? A:见下一节。

十、卸载

a2dismod ftp
a2disconf ftpd
rm -f /usr/lib/apache2/modules/mod_ftp.so /etc/apache2/mods-available/ftp.load /etc/apache2/conf-available/ftpd.conf /etc/apache2/ftpd.passwd
sed -i '/LoadModule ftp_module/d' /etc/apache2/apache2.conf
rm -rf /opt/httpd-ftp /usr/share/apache2/ftpdocs /etc/apache2/extra/ftpd.conf
apache2ctl configtest && systemctl restart apache2

回显:

Module ftp disabled.
Your MPM module, mpm_prefork, could not be found...
Syntax OK

说明:a2dismod 输出因系统而异,关键是最后 configtest 为 Syntax OK 且服务可启动。日志文件 /var/log/apache2/ftp_*.log 如需留证可自行备份后删除。

十一、参考链接