Ubuntu 22.04 安装 AnonAddy 1.7.3 指南
在 Ubuntu 22.04 真实服务器从零安装 AnonAddy(addy.io)v1.7.3:PHP 8.3 PPA + Nginx + MariaDB + Redis + Postfix 管道集成、队列与调度、邮件收发端到端实测与六条排障实录。
Ubuntu 22.04 安装 AnonAddy 1.7.3 指南
目标:在一台 Ubuntu 22.04 真实服务器上从零安装并跑通 AnonAddy(addy.io)v1.7.3:PHP 8.3 + Nginx + MariaDB + Redis + Postfix 完整依赖链、Web 控制台、邮件收发管道端到端验证。已在 Ubuntu 22.04 服务器实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04 LTS(Jammy),x86_64,systemd 托管
- 应用版本:AnonAddy v1.7.3(2026-10-06 发布,GitHub Releases 实查)
- 依赖组件实测版本:PHP 8.3.36(ondrej/php PPA)、Nginx 1.18.0、MariaDB 10.6.23、Redis 6.0.16、Postfix 3.6.4、Composer 2.10.3、Node.js 22.23.4
- 主机名约定:
mail.example.com;Web 控制台:app.example.com;应用目录/var/www/anonaddy - 演示管理员:
admin/q1w2e3r4(默认收件人admin@qq.com) - 网络说明:GitHub/getcomposer/NodeSource 均可直连下载;Composer 包经 Packagist 官方源安装成功
二、前置条件
- 一台干净的 Ubuntu 22.04 服务器(建议 ≥2C2G),hostname 已设为 FQDN:
hostnamectl set-hostname mail.example.com
hostname -f
实测回显:
mail.example.com
/etc/hosts增加本机解析(公网部署时改用真实 DNS A 记录):
echo "127.0.0.1 mail.example.com app.example.com" >> /etc/hosts
- 安全组/防火墙放行:25(SMTP)、80/443(Web)。注意:Vultr、UpCloud 等厂商默认封 25 端口,需开工单解封(官方 SELF-HOSTING 明示)。
三、版本选型说明
| 版本 | 发布日期 | 说明 |
|---|---|---|
| v1.7.3 | 2026-10-06 | 当前最新稳定版,本文采用 |
| v1.7.2 | 2026-09-02 | 前序补丁版 |
| v1.6.x | 2026-07 | 旧分支 |
关键约束来自 composer.json:"php": "^8.3" 且依赖 laravel/framework: ^13.0。Ubuntu 22.04 默认仓库只有 PHP 8.1,不满足要求,必须引入 ondrej/php PPA 安装 PHP 8.3——这是 Ubuntu 22.04 装 AnonAddy 与装普通 PHP 应用最大的差异点。
四、方案 A:官方源码在线安装(主方案)
4.1 安装基础环境
安装工具与 PPA 源:
apt-get update -qq
apt-get install -y software-properties-common ca-certificates curl gnupg lsb-release apt-transport-https
add-apt-repository -y ppa:ondrej/php
apt-get update -qq
实测回显(节选):
Adding deb entry to /etc/apt/sources.list.d/ondrej-ubuntu-php-jammy.list
Adding key to /etc/apt/trusted.gpg.d/ondrej-ubuntu-php.gpg with fingerprint B8DC7E53946656EFBCE4C1DD71DAEAAB4AD4CAB6
一次性安装全部服务组件(Postfix 以非交互方式选 “Internet Site”):
export DEBIAN_FRONTEND=noninteractive
echo "postfix postfix/mailname string mail.example.com" | debconf-set-selections
echo "postfix postfix/main_mailer_type string Internet Site" | debconf-set-selections
apt-get install -y nginx mariadb-server redis-server postfix \
php8.3-fpm php8.3-cli php8.3-mysql php8.3-mbstring php8.3-xml php8.3-bcmath \
php8.3-curl php8.3-gd php8.3-intl php8.3-zip php8.3-gmp php8.3-redis php8.3-opcache \
php8.3-mailparse supervisor unzip git cron rsyslog
⚠️ 排障实录:
php8.3-mailparse是 AnonAddy 依赖php-mime-mail-parser的硬性扩展,漏装会在 composer install 时报ext-mailparseplatform 错误,务必一次装齐。
安装 Node.js 22(前端构建需要,Ubuntu 仓库自带版本太旧):
curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh
bash /tmp/nodesource_setup.sh
apt-get install -y nodejs
node -v
实测回显:
v22.23.4
安装 Composer:
curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
composer --version
实测回显:
Composer version 2.10.3 2026-08-27 13:34:23
版本核验(逐条实测):
php -v | head -1 # PHP 8.3.36 (cli) (built: Oct 21 2026 14:31:50) (NTS)
nginx -v # nginx version: nginx/1.18.0 (Ubuntu)
mariadb --version # mariadb Ver 15.1 Distrib 10.6.23-MariaDB ...
redis-server --version
postconf mail_version
实测回显:
Redis server v=6.0.16 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=d6ed09173b889769
mail_version = 3.6.4
4.2 下载并解压 AnonAddy v1.7.3
cd /var/www
curl -sSL -o anonaddy.tar.gz https://github.com/anonaddy/anonaddy/archive/refs/tags/v1.7.3.tar.gz
tar -xzf anonaddy.tar.gz
mv anonaddy-1.7.3 anonaddy
ls /var/www/anonaddy | head
实测回显:
AGENTS.md
LICENSE.md
README.md
SECURITY.md
SELF-HOSTING.md
app
artisan
boost.json
bootstrap
composer.json
4.3 安装 PHP 依赖与构建前端
cd /var/www/anonaddy
composer install --no-dev --optimize-autoloader --no-interaction
npm ci --no-audit --no-fund
⚠️ 排障实录:vite.config.mjs 会读取
.env里的APP_URL,必须先建好.env再构建前端,否则报Cannot read properties of undefined (reading 'replace')。正确顺序是先做 4.4 再回来执行:
npm run production
实测回显(节选):
> production
> vite build
public/build/assets/app-YpEXFbPO.js 1,499.52 kB │ gzip: 440.71 kB
✓ built in 1.05s
4.4 配置 .env
cp .env.example .env
关键修改项(完整逐项说明见本专栏配置参数详解篇):
APP_NAME=AnonAddy
APP_URL=http://app.example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=addy_database
DB_USERNAME=addy
DB_PASSWORD=q1w2e3r4
MAIL_FROM_NAME=AnonAddy
MAIL_FROM_ADDRESS=mailer@example.com
MAIL_HOST=mail.example.com
MAIL_PORT=25
MAIL_EHLO_DOMAIN=mail.example.com
MAIL_VERIFY_PEER=false
ANONADDY_ADMIN_USERNAME=admin
ANONADDY_DOMAIN=example.com
ANONADDY_HOSTNAME=mail.example.com
ANONADDY_ALL_DOMAINS=example.com
ANONADDY_DNS_RESOLVER=127.0.0.1
ANONADDY_SECRET=<64 位随机十六进制串>
ANONADDY_GNUPGHOME=/var/www/anonaddy/.gnupg
SESSION_SECURE_COOKIE=false
ANONADDY_SECRET 用 php -r "echo bin2hex(random_bytes(32)).PHP_EOL;" 生成。
⚠️ 说明:
MAIL_VERIFY_PEER=false、SESSION_SECURE_COOKIE=false仅用于无受信证书的实验环境;生产环境使用 Let's Encrypt 证书后应恢复true并启用 HTTPS(APP_URL 改 https)。
4.5 数据库初始化与迁移
systemctl start mariadb
mariadb -e "CREATE DATABASE addy_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER \"addy\"@\"localhost\" IDENTIFIED BY \"q1w2e3r4\"; GRANT ALL PRIVILEGES ON addy_database.* TO \"addy\"@\"localhost\"; FLUSH PRIVILEGES;"
systemctl start redis-server
cd /var/www/anonaddy
php artisan key:generate
php artisan migrate --force
实测回显(迁移节选):
INFO Application key set successfully.
2026_06_04_105152_add_description_to_recipients_table .......... 5.77ms DONE
2026_09_25_142227_add_theme_to_users_table .................... 10.13ms DONE
2026_10_04_132200_add_show_intentional_failed_deliveries_to_users_table 6.40ms DONE
授权应用属主(Postfix 管道与 PHP-FPM 都以 www-data 运行):
chown -R www-data:www-data /var/www/anonaddy
php artisan storage:link
4.6 Nginx + PHP-FPM 站点
新建 /etc/nginx/sites-available/anonaddy:
server {
listen 80;
server_name app.example.com;
root /var/www/anonaddy/public;
index index.php;
client_max_body_size 25M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
启用并重启:
ln -sf /etc/nginx/sites-available/anonaddy /etc/nginx/sites-enabled/anonaddy
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl restart php8.3-fpm
systemctl restart nginx
systemctl is-active nginx
systemctl is-active php8.3-fpm
实测回显:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
active
active
生产缓存与首验:
php artisan config:cache
php artisan route:cache
php artisan view:cache
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: app.example.com" http://127.0.0.1/login
curl -s -H "Host: app.example.com" http://app.example.com/login | grep -oE "<title>[^<]*</title>"
实测回显:
200
<title>AnonAddy</title>
4.7 Postfix 邮件管道集成
这是 AnonAddy 与普通 PHP 应用最大的不同:Postfix 负责收信并把邮件管道给应用。安装 MySQL 查询支持并生成自签证书(生产换 Let's Encrypt):
apt-get install -y postfix-mysql acl
mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/mail.example.com.key \
-out /etc/nginx/ssl/mail.example.com.crt -subj "/CN=mail.example.com"
写 /etc/postfix/main.cf(核心项如下,完整文件见官方 SELF-HOSTING.md):
smtpd_banner = $myhostname ESMTP
biff = no
append_dot_mydomain = no
compatibility_level = 3.6
smtpd_tls_cert_file=/etc/nginx/ssl/mail.example.com.crt
smtpd_tls_key_file=/etc/nginx/ssl/mail.example.com.key
smtpd_use_tls=yes
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1
smtp_use_tls=yes
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1
myhostname = mail.example.com
mydomain = example.com
myorigin = /etc/mailname
mydestination = localhost.$mydomain, localhost
virtual_transport = anonaddy:
virtual_mailbox_domains = $mydomain, unsubscribe.$mydomain, mysql:/etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
recipient_delimiter = +
inet_interfaces = all
local_recipient_maps =
smtpd_helo_required = yes
smtpd_recipient_restrictions =
permit_mynetworks,
reject_unauth_destination,
check_policy_service unix:private/policy
disable_vrfy_command = yes
新建 /etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf(判断收件域是否归本服务):
user = addy
password = q1w2e3r4
hosts = 127.0.0.1
dbname = addy_database
query = SELECT (SELECT 1 FROM usernames WHERE '%s' IN (CONCAT(username, '.example.com'))) AS usernames, (SELECT 1 FROM domains WHERE domain = '%s' AND domain_verified_at IS NOT NULL) AS domains LIMIT 1;
chmod o= /etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf
chgrp postfix /etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf
⚠️ 排障实录:上述 query 行中的单引号必须原样保留。曾因 shell 引号转义丢单引号,Postfix 报
Unknown column 'example.com' in 'WHERE'、451 4.3.0 Temporary lookup failure拒收所有外发投递;用postmap -q "admin.example.com" mysql:/etc/postfix/...cf可独立验证,实测修复后回显1。
在 /etc/postfix/master.cf 末尾追加管道与访问策略服务:
# Pipe to addy.io application
anonaddy unix - n n - - pipe
flags=F user=www-data argv=php /var/www/anonaddy/artisan anonaddy:receive-email --sender=${sender} --recipient=${recipient} --local_part=${user} --extension=${extension} --domain=${domain} --size=${size}
# addy.io access policy
policy unix - n n - 0 spawn
user=www-data argv=php /var/www/anonaddy/postfix/AccessPolicy.php
echo example.com > /etc/mailname
postfix check
systemctl restart postfix
systemctl is-active postfix
ss -ltn | grep :25
实测回显:
active
LISTEN 0 100 0.0.0.0:25 0.0.0.0:*
LISTEN 0 100 [::]:25 [::]:*
4.8 Supervisor 队列与 cron 调度
转发邮件走 Redis 队列异步发送,必须有常驻 worker。新建 /etc/supervisor/conf.d/anonaddy-worker.conf:
[program:anonaddy-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/anonaddy/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/anonaddy-worker.log
stopwaitsecs=3600
Laravel 计划任务(清理失败投递、重置带宽、解析 Postfix 日志等)靠 cron 驱动:
echo "* * * * * www-data cd /var/www/anonaddy && php artisan schedule:run >> /dev/null 2>&1" > /etc/cron.d/anonaddy
systemctl restart supervisor
systemctl start cron
supervisorctl status
systemctl is-active cron
实测回显:
anonaddy-worker:anonaddy-worker_00 RUNNING pid 21023, uptime 0:00:03
anonaddy-worker:anonaddy-worker_01 RUNNING pid 21024, uptime 0:00:03
active
查看应用注册的全部计划任务:
runuser -u www-data -- php artisan schedule:list
实测回显(节选):
0 0 1 * * php artisan anonaddy:reset-bandwidth Next Due: 6 days from now
0 0 * * * php artisan anonaddy:check-domains-sending-verification Next Due: 21 hours from now
0 0 * * * php artisan anonaddy:check-domains-mx-validation Next Due: 21 hours from now
0 0 * * * php artisan anonaddy:clear-failed-deliveries Next Due: 21 hours from now
*/5 * * * * php artisan anonaddy:parse-postfix-mail-log Next Due: 44 seconds from now
4.9 端到端验证
第一步:创建管理员。收件邮箱必须通过 DNS 校验(Laravel email:rfc,dns 规则,域名需有 MX/A 记录),且不能用本服务的本地域:
cd /var/www/anonaddy
php artisan anonaddy:create-user admin admin@qq.com
实测回显:
Created user: "admin" with user_id: "7f113ba9-cb3b-459e-8d24-12910a2895db"
This user can now reset their password (the default password is their user_id)
初始密码即 user_id,登录后请立即修改;也可直接在数据库层重置为演示口令:
php artisan tinker
$u = App\Models\Username::where("username","admin")->first()->user;
$u->password = bcrypt("q1w2e3r4"); $u->save();
$r = $u->defaultRecipient; $r->email_verified_at = now(); $r->save();
⚠️ 排障实录:若用
admin@example.org之类无 MX 的保留域,命令报The email must be a valid email address.——该校验做真实 DNS 查询,实验环境需保证 DNS 可用(本文曾因上游解析对个别域返回 SERVFAIL 而失败,更换收件域后通过)。
第二步:创建别名(Web 控制台操作,或 tinker):
$u = App\Models\Username::where("username","admin")->first()->user;
$a = App\Models\Alias::create([
"id" => (string) Ramsey\Uuid\Uuid::uuid4(),
"user_id" => $u->id, "local_part" => "test1", "extension" => null,
"domain" => "example.com", "email" => "test1@example.com", "active" => true,
"emails_forwarded" => 0, "emails_blocked" => 0, "emails_replied" => 0, "emails_sent" => 0
]);
$a->recipients()->attach($u->defaultRecipient->id);
实测回显:alias created: test1@example.com
第三步:发信触发完整链路:
printf "From: sender@gmail.com\r\nTo: test1@example.com\r\nSubject: AnonAddy forwarding test 2\r\nMessage-ID: <test002@gmail.com>\r\n\r\nHello again.\r\n" > /tmp/test2.eml
sendmail -t < /tmp/test2.eml
sleep 4
tail -12 /var/log/mail.log
实测回显(核心链路逐行照抄):
Oct 25 02:23:08 mail postfix/smtpd[20983]: Anonymous TLS connection established from localhost[127.0.0.1]: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) ...
Oct 25 02:23:08 mail postfix/qmgr[20960]: CD9B6FE9667: from=<b_ny4g64lypezdi53dgnra_g4ytim3dgrrtc@example.com>, size=1894, nrcpt=1 (queue active)
Oct 25 02:23:08 mail postfix/smtpd[20983]: disconnect from localhost[127.0.0.1] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1 commands=7
Oct 25 02:23:09 mail postfix/smtp[20985]: Trusted TLS connection established to mx3.qq.com[157.255.4.243]:25: TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)
Oct 25 02:23:09 mail postfix/smtp[20985]: CD9B6FE9667: to=<admin@qq.com>, relay=mx3.qq.com[157.255.4.243]:25, delay=0.69, ..., dsn=5.0.0, status=bounced (host mx3.qq.com[157.255.4.243] said: 550 Mailbox not found. ...)
Oct 25 02:23:25 mail postfix/pipe[20973]: 8F946FE974E: to=<b_ny4g64lypezdi53dgnra_g4ytim3dgrrtc@example.com>, relay=anonaddy, delay=16, delays=16/0/0/0.14, dsn=2.0.0, status=sent (delivered via anonaddy service)
链路解读:sendmail 投信 → Postfix 经 anonaddy 管道交给应用 → 应用匹配别名后经 SMTP(TLS)投回本地 Postfix → Postfix 直连收件方 MX(qq.com 因演示邮箱不存在返回 550,属预期)→ 退信经 VERP 地址 b_...@example.com 回到管道入库为失败投递。整条「收件→别名匹配→转发→退信回执」闭环已跑通;真实邮箱场景下替换为真实收件人即可送达。
队列侧实测(worker 处理转发任务):
2026-10-25 02:23:08 App\Mail\ForwardEmail .......................... RUNNING
2026-10-25 02:23:08 App\Mail\ForwardEmail .................... 103.64ms DONE
第四步:Web 登录验证:
TOKEN=$(curl -s -c /tmp/cj -H "Host: app.example.com" http://app.example.com/login | grep -oE 'value="[^"]{30,}"' | head -1 | cut -d'"' -f2)
curl -s -b /tmp/cj -c /tmp/cj -o /dev/null -w "%{http_code} -> %{redirect_url}\n" -H "Host: app.example.com" --data-urlencode "_token=$TOKEN" --data-urlencode "username=admin" --data-urlencode "password=q1w2e3r4" http://app.example.com/login
curl -s -b /tmp/cj -H "Host: app.example.com" http://app.example.com/aliases -o /tmp/aliases.html -w "%{http_code}\n"
grep -c "test1@example.com" /tmp/aliases.html
实测回显:
302 -> http://app.example.com/
200
1
五、方案 B:Docker 镜像安装(备选)
官方维护一体化镜像 anonaddy/anonaddy:1.7.3(内置 Nginx + PHP-FPM + Postfix + cron),配合 MariaDB/Redis 用 Compose 编排即可,适合不想手工维护依赖链的场景。完整编排与实测见本专栏《AnonAddy 1.7.3 Docker Compose 部署指南》。
六、方案对比与选择建议
| 维度 | 方案 A 源码安装 | 方案 B Docker 镜像 |
|---|---|---|
| 依赖控制 | 完全自控(PPA/版本自选) | 镜像内置,固定 |
| 运维成本 | 高(PHP/Nginx/Postfix 逐层维护) | 低(单镜像多进程) |
| 适合 | 生产、需定制 Rspamd/加固 | 快速起步、测试、NAS |
| 端口 | 25/80/443 直挂宿主 | 25/8000 映射 |
建议:正式对外服务选方案 A 并按官方文档补 Rspamd 与 DKIM(见本专栏配置参数详解篇);体验与小规模自用选方案 B。
七、配置说明
.env 逐项参数(限额、带宽、DKIM 签名密钥、SPF/DKIM/DMARC 加固)见本专栏《AnonAddy 1.7.3 配置参数详解》;日常 artisan 运维命令见《AnonAddy 1.7.3 常用 CLI 命令详解》。最常改的三项:ANONADDY_DOMAIN(主域)、ANONADDY_ADMIN_USERNAME(管理员用户名)、MAIL_VERIFY_PEER(证书校验开关)。
八、常见问题
Q1:composer install 报 ext-mailparse 缺失?
安装 php8.3-mailparse 后重跑即可,实测 php -m | grep mailparse 应输出 mailparse。
Q2:npm run production 报 Cannot read properties of undefined (reading 'replace')?
vite.config.mjs 读 .env 的 APP_URL,先 cp .env.example .env 并填好 APP_URL 再构建。
Q3:创建用户报 The email must be a valid email address.?
两个原因:收件域是 ANONADDY_DOMAIN/ANONADDY_ALL_DOMAINS 内的本地域(规则禁止);或收件域无 MX/A 记录、本机 DNS 查询失败。换真实可解析域,并确认 getent hosts <域> 正常。
Q4:登录返回 419 Page Expired?
HTTP 环境下 SESSION_SECURE_COOKIE=true 导致浏览器/curl 不回传会话 Cookie。实验环境改 SESSION_SECURE_COOKIE=false 并 php artisan config:cache;生产启用 HTTPS 后恢复 true。
Q5:管道日志报 storage/logs/laravel-*.log could not be opened: Permission denied?
以 root 跑过 artisan 导致日志文件属 root,www-data 管道写不进去。执行 chown -R www-data:www-data /var/www/anonaddy/storage /var/www/anonaddy/bootstrap/cache 后 postqueue -f 重投。
Q6:外发投递 451 Temporary lookup failure?
/etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf 的 query 单引号丢失或库账号不通。用 postmap -q "admin.example.com" mysql:/etc/postfix/mysql-virtual-alias-domains-and-subdomains.cf 定位,正常返回 1。
九、卸载与清理
systemctl stop supervisor
rm -f /etc/supervisor/conf.d/anonaddy-worker.conf /etc/cron.d/anonaddy
rm -f /etc/nginx/sites-enabled/anonaddy /etc/nginx/sites-available/anonaddy
systemctl restart nginx
sed -i '/# Pipe to addy.io application/,+2d; /# addy.io access policy/,+1d' /etc/postfix/master.cf
systemctl restart postfix
mariadb -e "DROP DATABASE addy_database; DROP USER 'addy'@'localhost';"
rm -rf /var/www/anonaddy
按需卸载 PHP PPA、Node、MariaDB、Redis、Postfix。