静态站的服务器配置,大部分人抄来的模板里塞了一堆用不上的东西:反向代理、负载均衡、PHP 转发…… 我这个站连后端都没有,那些全是噪音。下面这份是我实际在跑的配置,删到剩骨头, 但该有的安全项都在。

先说前提

假设域名已经解析到服务器 IP,证书已经签好,站点文件放在 /var/www/mysite。 证书我用的是 acme.sh 签的免费证书,存放在 /etc/nginx/ssl/ 下。

如果是第一次装 Nginx,在 Ubuntu/Debian 上是:

sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx

CentOS / 阿里云 Linux 换一下包管理器就行,注意 CentOS 8 之后默认源里的 Nginx 版本偏老, 建议加官方源。

完整配置

新建 /etc/nginx/conf.d/mysite.conf(不要动 nginx.conf 主文件), 内容如下:

# ---------- HTTP:全部跳转到 HTTPS ----------
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # 证书续期的验证目录要放行,否则续签会失败
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

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

# ---------- HTTPS:静态站主体 ----------
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com www.example.com;

    root /var/www/mysite;
    index index.html;

    # 证书
    ssl_certificate     /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # 基础安全头
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options SAMEORIGIN;
    add_header Referrer-Policy strict-origin-when-cross-origin;

    # gzip:文本类才需要压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/javascript
               application/json image/svg+xml;

    # HTML 不缓存,保证改完立刻生效
    location ~* \.html$ {
        add_header Cache-Control "no-cache, must-revalidate";
    }

    # 静态资源长缓存(文件名不变就不该变)
    location ~* \.(css|js|svg|png|jpg|jpeg|webp|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 禁止访问隐藏文件(.git / .env 之类)
    location ~ /\. {
        deny all;
        return 404;
    }

    # 404 兜底
    error_page 404 /404.html;
    location = /404.html {
        internal;
    }
}

存盘后先测语法,通过了再重载。这一步千万别省——配置写错直接 reload,站会当场挂掉。

sudo nginx -t
sudo systemctl reload nginx

几个容易踩的点

1. 证书续期的验证目录必须放行

我在 HTTP 那个 server 块里单独放行了 /.well-known/acme-challenge/。 如果整站 301 到 HTTPS 而没有这个例外,acme.sh 的续签会一直失败, 而失败通常不会立刻发现——等到三个月后证书过期,浏览器一片红,才知道出事。

续期本身交给定时任务,acme.sh 装完会自动加一条 cron,可以用 crontab -l 确认。手动测试续期:

acme.sh --renew -d example.com --force

2. HTML 一定不要设长缓存

静态站最烦人的体验是:内容改了,用户看到的还是旧的。 原因是浏览器把 HTML 缓存住了。我的做法是 HTML 用 no-cache, CSS 和图片用 30 天。这样内容立刻生效,样式资源又不浪费带宽。

代价是每次改样式,用户可能还在用缓存里的旧 CSS。解决办法是给文件名加版本号, 比如 style.css?v=2——注意这时候 immutable 就要去掉,否则会冲突。 简单点就直接改文件名,style-2.css。

3. http2 的写法变了

Nginx 1.25.1 之后,listen 443 ssl http2; 这种写法会报 warning, 官方推荐改成独立的 http2 on;。老版本不认这个指令,会报 unknown directive。 先看一眼版本:

nginx -v

低于 1.25.1 就把 http2 on; 删掉,改回 listen 443 ssl http2;。

4. 根目录权限

Nginx 的 worker 进程通常以 www-data(或 nginx)身份跑。 如果站点目录属主是 root 且权限是 700,会直接返回 403,日志里只写 Permission denied, 很容易误以为是配置问题。修一下:

sudo chown -R www-data:www-data /var/www/mysite
sudo find /var/www/mysite -type d -exec chmod 755 {} \;
sudo find /var/www/mysite -type f -exec chmod 644 {} \;

要不要上 CDN

我的判断是不需要。这个站的页面加起来不到 100KB,服务器在国内, 访问速度本来就够。上了 CDN 反而多一层要维护的东西——缓存刷新、回源配置、HTTPS 证书同步, 出问题时排查链路还变长了。

如果哪天访问量真的大起来,或者有大量图片,再考虑。现在没必要。