Alpine 使用 Nginx 搭建 HTTPS 静态文件浏览服务

在 Alpine Linux 上使用 Nginx 提供 HTTPS 文件目录浏览,配置 Basic Auth、敏感文件拦截、日志轮转、Gzip、限流、强制下载和 BBR。

在 Alpine Linux 下,如果只是临时分享目录,python3 -m http.server 很方便;但如果要长期对外提供文件浏览和下载,Nginx 更适合。它可以处理 HTTPS、目录浏览、Basic Auth、访问日志、限流、安全响应头和强制下载,资源占用也很低。

本文以 fetch.jgaga.tk 为示例域名,Web 根目录为 /var/www/fetch.jgaga.tk。实际使用时,把域名和目录替换成自己的即可。

准备证书

正式对外提供 HTTPS 之前,需要先给域名签发证书。可以使用你现有的一键脚本,也可以使用 acme.shlegocertbot

本文后续示例假设证书和私钥已经放在下面的位置:

/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.crt
/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.key

如果你的一键脚本生成的是其他路径,只需要修改 Nginx 配置里的 ssl_certificatessl_certificate_key

安装并启用 Nginx

Alpine 使用 apk 安装软件,服务管理使用 OpenRC:

apk update
apk add nginx openssl logrotate
rc-update add nginx default

创建 Web 根目录:

mkdir -p /var/www/fetch.jgaga.tk
chown -R root:nginx /var/www/fetch.jgaga.tk

这里推荐让目录归属为 root:nginx。Nginx worker 以 nginx 用户运行,只需要读取文件即可;后面如果要让普通用户上传文件,再单独调整目录权限。

创建认证密码文件

如果目录要对外开放,建议先加 Basic Auth。下面以用户名 admin 为例:

printf "admin:%s\n" "$(openssl passwd -apr1)" > /etc/nginx/.htpasswd
chown root:nginx /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd

执行第一条命令时,系统会提示输入并确认密码,输入过程不会显示字符,这是正常的。

如果要换用户名,修改冒号前面的 admin 后重新生成即可:

printf "your_username:%s\n" "$(openssl passwd -apr1)" > /etc/nginx/.htpasswd
chown root:nginx /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd

创建日志目录

为了让日志路径和后面的 logrotate 规则一致,先显式创建 Nginx 日志目录:

mkdir -p /var/log/nginx
chown root:nginx /var/log/nginx
chmod 755 /var/log/nginx

Nginx 完整配置

编辑 /etc/nginx/nginx.conf,可以按下面这份配置整理。它包含:

nvim /etc/nginx/nginx.conf
  • HTTP 自动跳转 HTTPS
  • Let’s Encrypt ACME 验证目录放行
  • TLS 1.2 / TLS 1.3
  • HTTP/2
  • 目录浏览
  • Basic Auth
  • 敏感文件拦截
  • Gzip
  • 单 IP 并发、请求频率和下载速度限制
  • 指定格式强制下载
user nginx;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    sendfile on;
    keepalive_timeout 65;

    gzip on;
    gzip_vary on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/x-javascript application/xml application/xml+rss application/x-sh application/x-yaml;

    # 限速与请求频率的共享内存区域定义
    limit_conn_zone $binary_remote_addr zone=addr_conn:10m;
    limit_req_zone  $binary_remote_addr zone=addr_req:10m rate=5r/s;

    # ========================================================
    # 1. 80 端口:负责 Let's Encrypt 证书验证及全站强制跳转 HTTPS
    # ========================================================
    server {
        listen 80;
        listen [::]:80;
        server_name fetch.jgaga.tk;

        location /.well-known/acme-challenge/ {
            root /var/www/fetch.jgaga.tk;
        }

        location / {
            return 301 https://$host$request_uri;
        }
    }

    # ========================================================
    # 2. 10521 端口:全站安全、密码认证、完全去重后的限速下载服务
    # ========================================================
    server {
        listen 10521 ssl;
        listen [::]:10521 ssl;
        http2 on;
        server_name fetch.jgaga.tk;

        # SSL 证书配置
        ssl_certificate     /etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.crt;
        ssl_certificate_key /etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.key;

        # SSL 安全参数
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;
        ssl_session_timeout 1d;
        ssl_session_cache shared:MozSSL:10m;
        ssl_session_tickets off;

        # 【优化】提取至 server 全局:全站通用的安全响应头
        add_header Strict-Transport-Security "max-age=63072000" always;
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-XSS-Protection "1; mode=block";

        # 【优化】提取至 server 全局:网页与文件下载统一要求密码认证
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;

        # 【优化】提取至 server 全局:统一限速规则
        # 默认方案:前 500MB 全速,后续 500KB/s,适合公开下载或带宽有限的 VPS
        limit_conn addr_conn 3;
        limit_req zone=addr_req burst=10 nodelay;
        limit_rate_after 500m;
        limit_rate 500k;
        # 不限速方案:如果只想限制连接数/请求频率,不限制下载速度,
        # 可以保留上面的 limit_conn / limit_req,并注释或删除上面两行限速指令:
        # limit_rate_after 500m;
        # limit_rate 500k;

        # 网站根目录
        root /var/www/fetch.jgaga.tk;
        index index.html index.htm;

        # 拒绝访问以 . 开头的隐藏文件(如 .htaccess, .git 等)
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }

        # 拒绝访问敏感后缀文件
        location ~ \.(key|pem|conf|sh|sql)$ {
            deny all;
            access_log off;
            log_not_found off;
        }

        # 核心处理块:包含全站文件浏览列表
        location / {
            autoindex on;
            autoindex_exact_size off;
            autoindex_localtime on;
            charset utf-8;

            # 【无重绝招】如果是特定的媒体或文档文件,直接在此强制触发下载。
            # 这样既实现了图片/视频强制下载,又完美继承了上面所有的安全头、认证和限速,绝无重复代码。
            if ($request_filename ~* \.(jpg|jpeg|png|gif|mp4|mkv|avi|mp3|pdf|txt)$) {
                add_header Content-Disposition "attachment";
            }
        }
    }
}

注意:使用正则 location 匹配强制下载文件时,请把认证和限流也写进去。否则直接访问这些文件 URL 时,可能绕过 location / 中的控制规则。

测试并启动服务

nginx -t
rc-service nginx start

后续修改配置后,使用 reload 即可:

nginx -t
rc-service nginx reload

打开 https://fetch.jgaga.tk:10521 后,应该会先弹出 Basic Auth 登录框。认证通过后,可以看到 Nginx 的目录列表。

修复点击文件 403 的权限问题

如果目录列表能显示文件名,但点击 .jpg 等文件时返回 403 Forbidden,通常是文件本身权限不足。autoindex 能列目录,不代表 Nginx 一定能读取文件内容。

推荐先把目录所有权整理为 root:nginx

chown -R root:nginx /var/www/fetch.jgaga.tk

如果仍然有权限问题,再统一修正目录和文件权限:

find /var/www/fetch.jgaga.tk -type d -exec chmod 755 {} \;
find /var/www/fetch.jgaga.tk -type f -exec chmod 644 {} \;

执行完不需要重启 Nginx,刷新浏览器即可。

让普通用户管理文件目录

普通用户不能直接运行当前这套 Nginx 服务,因为 80 和 443 属于特权端口,证书私钥和系统日志也需要更高权限。正确做法是:Nginx 仍由 root 启动,worker 自动降权为 nginx;普通用户只负责上传和管理 Web 根目录里的文件。

假设普通用户是 felix,先把它加入 nginx 组:

addgroup felix nginx

再允许同组用户写入目录:

chmod -R 775 /var/www/fetch.jgaga.tk
chmod g+s /var/www/fetch.jgaga.tk

chmod g+s 会让目录中新建的文件继承 nginx 组,避免后续文件归属变得混乱。

修改用户组后,旧 SSH 会话不会自动刷新身份。重新登录,或在当前终端执行:

su - felix

然后用 id 确认输出里包含 nginx 组。

配置日志按天轮转

Alpine 没有 FreeBSD 的 newsyslog,这里使用 logrotate。前面已经通过 apk add logrotate 安装。

创建 /etc/logrotate.d/nginx

/var/log/nginx/*.log {
    daily
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    create 0644 root nginx
    sharedscripts
    postrotate
        [ -s /run/nginx/nginx.pid ] && kill -USR1 "$(cat /run/nginx/nginx.pid)"
    endscript
}

参数说明:

  • daily:每天轮转。
  • rotate 30:保留最近 30 天日志。
  • compressdelaycompress:压缩旧日志,并延后一轮压缩当前刚切走的文件。
  • create 0644 root nginx:轮转后创建新的日志文件。
  • kill -USR1:通知 Nginx master 重新打开日志文件,无需重启服务。

测试配置:

logrotate -d /etc/logrotate.d/nginx

如果没有语法错误,即可交给系统定时任务自动执行。Alpine 通常通过 /etc/periodic/daily/logrotate 调用 logrotate;如果你的系统没有这个文件,可以手动确认 crond 是否启用:

rc-update add crond default
rc-service crond start

强制下载指定文件类型

如果希望点击图片、视频、PDF、文本文件时不要在浏览器中预览,而是直接弹出下载,可以使用:

location ~* \.(jpg|jpeg|png|gif|mp4|mkv|avi|mp3|pdf|txt)$ {
    add_header Content-Disposition "attachment";

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;

    limit_conn addr_conn 3;
    limit_req zone=addr_req burst=10 nodelay;

    # 默认限速:前 500MB 全速,后续 500KB/s。
    # 如果要不限速,注释或删除下面两行即可。
    limit_rate_after 500m;
    limit_rate 500k;
}

如果你的 HTTPS server 里已经有全局安全响应头,建议也在这个正则 location 中补齐一份 add_header。Nginx 的 add_header 在不同层级混用时容易出现继承行为不符合直觉的情况,直接写全更稳。

可选:内网免密、外网认证

如果以后想让内网直接访问,外网才需要密码,可以在 location / 中使用 satisfy any

location / {
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;
    charset utf-8;

    satisfy any;
    allow 192.168.100.0/24;
    allow 10.20.20.0/24;
    allow fd88::/64;
    allow 127.0.0.1;
    deny all;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

本文完整配置没有启用这段规则。如果你没有固定内网网段需求,保持 Basic Auth 全局生效即可。

可选:配合 fail2ban 封禁限流 IP

Nginx 触发 limit_reqlimit_conn 后,会在错误日志里写入 limiting requestslimiting connections。可以用 fail2ban 继续联动封禁。

安装:

apk add fail2ban
rc-update add fail2ban default

创建 /etc/fail2ban/filter.d/nginx-limit.conf

[Definition]
failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting (requests|connections).*? client: <HOST>,
ignoreregex =

/etc/fail2ban/jail.local 中加入:

[nginx-limit]
enabled  = true
filter   = nginx-limit
port     = http,https
logpath  = /var/log/nginx/error.log
findtime = 60
maxretry = 5
bantime  = 3600

测试正则并重载:

fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit.conf
rc-service fail2ban restart

Alpine 常见防火墙可能是 nftablesiptablesawall,封禁动作要按你实际使用的防火墙调整。

可选:开启 Linux BBR

Alpine 是 Linux 系统,BBR 的开启方式和 FreeBSD 不同。先确认内核支持:

sysctl net.ipv4.tcp_available_congestion_control

如果输出里包含 bbr,可以临时开启:

modprobe tcp_bbr 2>/dev/null || true
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr

验证:

sysctl net.ipv4.tcp_congestion_control

如果输出为下面这样,就说明已经生效:

net.ipv4.tcp_congestion_control = bbr

永久生效:

cat > /etc/sysctl.d/99-bbr.conf <<'EOF'
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
EOF

sysctl --system

如果你的 Alpine 内核没有内置或加载 BBR,sysctl 会报 No such file or directory 或可用算法中没有 bbr。这种情况需要更换支持 BBR 的内核,不能只靠 Nginx 配置解决。

总结

这套配置可以把 Alpine 上的一个普通目录整理成可长期使用的 HTTPS 文件浏览服务:

  • Nginx 负责目录浏览和静态文件传输。
  • TLS 与安全响应头保证基础 HTTPS 安全性。
  • Basic Auth 防止目录裸奔。
  • 敏感文件拦截避免 .htpasswd、私钥、配置文件等被访问。
  • logrotate 负责日志轮转。
  • Gzip 和限流分别处理传输效率与带宽保护。
  • 强制下载规则让图片、视频、PDF 等文件点击后直接下载。

完成配置后,记得每次修改都先执行:

nginx -t

确认语法无误后再 reload。