在 FreeBSD 15 下,如果只是临时分享目录,python3 -m http.server 很方便;但如果要长期对外提供文件浏览和下载,Nginx 更适合。它可以直接处理 HTTPS、Basic Auth、目录浏览、访问日志、限流和安全响应头,性能和可控性都更好。
本文以 fetch.jgaga.tk 为示例域名,Web 根目录为 /usr/local/www/fetch.jgaga.tk。实际使用时,把域名和目录替换成自己的即可。
准备证书
正式对外提供 HTTPS 之前,需要先为域名签发证书。可以使用你现有的一键脚本,或使用 acme.sh、certbot 等工具签发。
本文后续示例假设证书和私钥已经放在下面的位置:
/usr/local/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.crt
/usr/local/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.key如果你的一键脚本生成的是其他路径,修改 Nginx 配置里的 ssl_certificate 和 ssl_certificate_key 即可。
安装并启用 Nginx
pkg install -y nginx
sysrc nginx_enable="YES"创建 Web 根目录:
mkdir -p /usr/local/www/fetch.jgaga.tk
chown -R root:www /usr/local/www/fetch.jgaga.tk这里推荐让目录归属为 root:www。Nginx worker 以 www 用户运行,只需要读取文件即可;后面如果要让普通用户上传文件,再单独调整目录权限。
创建认证密码文件
如果目录要对外开放,建议先加 Basic Auth。FreeBSD 自带 openssl,不需要额外安装工具。
下面以用户名 admin 为例:
printf "admin:%s\n" "$(openssl passwd -apr1)" > /usr/local/etc/nginx/.htpasswd
chown root:www /usr/local/etc/nginx/.htpasswd
chmod 640 /usr/local/etc/nginx/.htpasswd执行第一条命令时,系统会提示输入并确认密码,输入过程不会显示字符,这是正常的。
如果要换用户名,修改冒号前面的 admin 后重新生成即可:
printf "your_username:%s\n" "$(openssl passwd -apr1)" > /usr/local/etc/nginx/.htpasswd
chown root:www /usr/local/etc/nginx/.htpasswd
chmod 640 /usr/local/etc/nginx/.htpasswd创建日志目录
为了让日志路径和后面的 newsyslog 轮转规则一致,先显式创建 Nginx 日志目录:
mkdir -p /var/log/nginx
chown root:wheel /var/log/nginx
chmod 755 /var/log/nginxNginx 完整配置
编辑 /usr/local/etc/nginx/nginx.conf,可以按下面这份配置整理。它包含:
- HTTP 自动跳转 HTTPS
- Let’s Encrypt ACME 验证目录放行
- TLS 1.2 / TLS 1.3
- HTTP/2
- 目录浏览
- Basic Auth
- 敏感文件拦截
- Gzip
- 单 IP 并发、请求频率和下载速度限制
- 指定格式强制下载
user www;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include 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;
server {
listen 80;
listen [::]:80;
server_name fetch.jgaga.tk;
location /.well-known/acme-challenge/ {
root /usr/local/www/fetch.jgaga.tk;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name fetch.jgaga.tk;
ssl_certificate /usr/local/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.crt;
ssl_certificate_key /usr/local/etc/ssl/fetch.jgaga.tk/fetch.jgaga.tk.key;
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;
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";
root /usr/local/www/fetch.jgaga.tk;
index index.html index.htm;
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;
auth_basic "Restricted Access";
auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
limit_conn addr_conn 3;
limit_req zone=addr_req burst=10 nodelay;
limit_rate_after 50m;
limit_rate 500k;
}
location ~* \.(jpg|jpeg|png|gif|mp4|mkv|avi|mp3|pdf|txt)$ {
add_header Content-Disposition "attachment";
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";
auth_basic "Restricted Access";
auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
limit_conn addr_conn 3;
limit_req zone=addr_req burst=10 nodelay;
limit_rate_after 50m;
limit_rate 500k;
}
}
}注意:使用正则 location 匹配强制下载文件时,请把认证和限流也写进去。否则直接访问这些文件 URL 时,可能绕过 location / 中的控制规则。
测试并启动服务
nginx -t
service nginx start后续修改配置后,使用 reload 即可:
nginx -t
service nginx reload打开 https://fetch.jgaga.tk 后,应该会先弹出 Basic Auth 登录框。认证通过后,可以看到 Nginx 的目录列表。
修复点击文件 403 的权限问题
如果目录列表能显示文件名,但点击 .jpg 等文件时返回 403 Forbidden,通常是文件本身权限不足。autoindex 能列目录,不代表 Nginx 一定能读取文件内容。
推荐先把目录所有权整理为 root:www:
chown -R root:www /usr/local/www/fetch.jgaga.tk如果仍然有权限问题,再统一修正目录和文件权限:
find /usr/local/www/fetch.jgaga.tk -type d -exec chmod 755 {} \;
find /usr/local/www/fetch.jgaga.tk -type f -exec chmod 644 {} \;执行完不需要重启 Nginx,刷新浏览器即可。
让普通用户管理文件目录
普通用户不能直接运行当前这套 Nginx 服务,因为 80 和 443 属于特权端口,证书私钥和系统日志也需要更高权限。正确做法是:Nginx 仍由 root 启动,worker 自动降权为 www;普通用户只负责上传和管理 Web 根目录里的文件。
假设普通用户是 felix,先把它加入 www 组:
pw groupmod www -m felix再允许同组用户写入目录:
chmod -R 775 /usr/local/www/fetch.jgaga.tk
chmod g+s /usr/local/www/fetch.jgaga.tkchmod g+s 会让目录中新建的文件继承 www 组,避免后续文件归属变得混乱。
修改用户组后,旧 SSH 会话不会自动刷新身份。重新登录,或在当前终端执行:
su - felix然后用 id 确认输出里包含 www 组。
配置日志按天轮转
FreeBSD 不需要额外安装 logrotate,系统自带的 newsyslog 就能完成日志轮转。
创建自定义规则目录:
mkdir -p /usr/local/etc/newsyslog.conf.d/编辑 /usr/local/etc/newsyslog.conf.d/nginx.conf:
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
/var/log/nginx/*.log root:wheel 644 30 * @T00 JC /var/run/nginx.pid 30参数说明:
count 30:保留最近 30 天日志。@T00:每天 00:00 轮转。J:使用 bzip2 压缩旧日志。C:日志不存在时自动创建。30:轮转后向 Nginx master 发送SIGUSR1,让 Nginx 重新打开日志文件,无需重启服务。
测试配置:
newsyslog -nv如果看到类似下面的行,说明配置已被识别:
Processing /usr/local/etc/newsyslog.conf.d/nginx.conf如果提示 /var/log/nginx/*.log 不存在,多半是 Nginx 还没产生日志,或干跑模式没有实际创建文件。只要没有语法错误即可。
强制下载指定文件类型
如果希望点击图片、视频、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 /usr/local/etc/nginx/.htpasswd;
limit_conn addr_conn 3;
limit_req zone=addr_req burst=10 nodelay;
limit_rate_after 50m;
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 /usr/local/etc/nginx/.htpasswd;
}本文完整配置没有启用这段规则。如果你没有固定内网网段需求,保持 Basic Auth 全局生效即可。
可选:配合 fail2ban 封禁限流 IP
Nginx 触发 limit_req 或 limit_conn 后,会在错误日志里写入 limiting requests 或 limiting connections。可以用 fail2ban 继续联动封禁。
创建 /usr/local/etc/fail2ban/filter.d/nginx-limit.conf:
[Definition]
failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting (requests|connections).*? client: <HOST>,
ignoreregex =在 /usr/local/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
banaction = pf测试正则并重载:
fail2ban-regex /var/log/nginx/error.log /usr/local/etc/fail2ban/filter.d/nginx-limit.conf
fail2ban-client reloadbanaction = pf 需要系统已经正确启用并配置 PF 防火墙。
可选:开启 FreeBSD BBR
FreeBSD 的 BBR 不是简单切换一个拥塞控制算法,而是切换到一个 TCP stack。它适合跨国、高延迟或存在丢包的链路,可能改善下载吞吐。
临时开启:
kldload tcp_bbr
sysctl net.inet.tcp.functions_default=bbr验证:
sysctl net.inet.tcp.functions_default如果输出为下面这样,就说明已经生效:
net.inet.tcp.functions_default: bbr永久生效:
echo 'tcp_bbr_load="YES"' >> /boot/loader.conf
echo 'net.inet.tcp.functions_default=bbr' >> /etc/sysctl.conf总结
这套配置可以把 FreeBSD 15 上的一个普通目录整理成可长期使用的 HTTPS 文件浏览服务:
- Nginx 负责目录浏览和静态文件传输。
- TLS 与安全响应头保证基础 HTTPS 安全性。
- Basic Auth 防止目录裸奔。
- 敏感文件拦截避免
.htpasswd、私钥、配置文件等被访问。 newsyslog负责日志轮转。- Gzip 和限流分别处理传输效率与带宽保护。
- 强制下载规则让图片、视频、PDF 等文件点击后直接下载。
完成配置后,记得每次修改都先执行:
nginx -t确认语法无误后再 reload。