出问题时按「我干了什么」找,不用背命令
反向代理、跨域、gzip、SPA 兜底 —— 都是「配了却没生效」的重灾区
proxy_pass 末尾带不带斜杠,路径拼接结果完全不同:带 / 会剥掉 location 前缀。
location /api/ {
proxy_pass http://127.0.0.1:8080/; # 末尾 / → 剥掉 /api 前缀
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}预检请求是 OPTIONS,必须在 location 里显式 return 204,否则 405 会让浏览器直接判失败。
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
}gzip 对已压缩格式(png/jpg/woff2)没意义,还会浪费 CPU;只在 nginx 直接返回文本时才有明显收益。
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/css application/javascript application/json
application/xml image/svg+xml;try_files 的 $uri/ 一旦命中真实目录就交给 index 模块,目录里没有 index.html 会直接 403,不会回退。
root /var/www/app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源加长缓存(文件名带 hash 才敢这么写)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}裸域跳 www 必须在 80 和 443 两个 server 块里都写,只写 80 会让 https 访问裸域落到证书错误。
server {
listen 80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme; # 放 dist 之外,避免部署时被 --delete 删掉
try_files $uri =404;
}
location / { return 301 https://www.example.com$request_uri; }
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
return 301 https://www.example.com$request_uri;
}这些头是否存在,用响应头分析工具粘贴检查即可,不必自己写脚本。
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # CSP 一开始别写太严,先 report-only 观察 add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;
多阶段构建、非 root、时区 —— 影响镜像体积与线上行为的三件事
构建依赖留在 builder 阶段,最终镜像只有静态产物 + nginx,体积能从 1GB+ 降到 50MB 级。
FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:1.27-alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
容器默认是 root。加一行 USER 就能避免容器逃逸后直接拿到宿主机 root。
RUN addgroup -S app && adduser -S -G app app WORKDIR /app COPY --chown=app:app . . USER app
先 COPY 依赖清单再装依赖,最后才 COPY 源码 —— 改代码时不会让依赖层失效。
# 好:依赖层可复用 COPY package.json package-lock.json ./ RUN npm ci COPY . . # 坏:一行 COPY . . 会让任何代码改动都重装依赖 # COPY . . # RUN npm ci
Alpine 默认 UTC,日志时间比本地时间差 8 小时是常见困惑来源。
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
ENV TZ=Asia/Shanghai按「我干了什么蠢事」组织。先想清楚你做了什么,再挑对应那条。
先在新分支上留住提交,再把原分支退回去。cherry-pick 比 reset 安全,因为不会丢东西。
git branch feature-x # 在当前提交处建分支 git reset --hard origin/main # 原分支退回远端状态 git switch feature-x # 提交已经在 feature-x 上了
被 reset 掉的提交不会立刻消失,reflog 里还有。找到那一条再 reset 回去。
git reflog # 找到 reset 之前的那条 HEAD@{n}
git reset --hard HEAD@{3} # 退回到那一刻
# 只想捞回某个文件:
git checkout HEAD@{3} -- path/to/filerebase 中途退出有两个方向:回到开始前,或者跳过这个提交继续。
git rebase --abort # 完全放弃,回到 rebase 之前 git rebase --skip # 跳过当前这个冲突的提交,继续 git rebase --continue # 解决完冲突后继续
rebase 改写了历史,普通 push 必然被拒。用 --force-with-lease:它会在远端被别人更新过时拒绝推送,比 --force 安全得多。
git push --force-with-lease origin feature-x # 不要用 --force:它会把别人刚推的提交一起覆盖掉
本地删除后还能从 reflog 恢复;如果从未推送过,尽快操作,gc 之后会真的没了。
git reflog # 找分支最后一次提交 git branch recovered-branch <commit-ish> # 用它重建分支
stash 不会丢,列出全部再挑那条恢复,不需要记住编号顺序。
git stash list
git stash show -p stash@{2} # 先看内容确认是那一条
git stash pop stash@{2} # 恢复并从栈中删除
git stash apply stash@{2} # 恢复但保留在栈里(更保险)最重要的一条:把 key 换掉。历史可以改写,但泄露过的 key 必须视为已泄露。删除历史只防止后续扩散。
# 1) 先去平台把该 key 作废并重新生成(这一步不能省) # 2) 再从历史里移除(需要强推,会改写所有协作者的克隆) git filter-repo --path .env --invert-paths # 或只从最新提交移除,后续提交补 .gitignore git rm --cached .env && echo ".env" >> .gitignore && git commit -m "chore: remove .env"
merge 冲突时选了 theirs 就会丢掉自己的改动。用 reflog 回到 merge 之前重做,而不是手工再改一遍。
git reflog # 找 merge 之前的 HEAD
git reset --hard HEAD@{1} # 回到 merge 前
git merge --no-commit origin/x # 重新 merge,冲突逐个看清再解