Ищите по тому, что вы только что сделали, а не по названию команды
Reverse proxy, CORS, gzip, SPA fallback — the usual "configured but not working" traps
A trailing slash on proxy_pass changes path joining: with "/", the location prefix is stripped.
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;
}Preflight is an OPTIONS request; return 204 explicitly or a 405 makes the browser fail the request.
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 is useless for already-compressed formats (png/jpg/woff2) and wastes CPU; it only helps for text served directly.
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;Once $uri/ matches a real directory, index handling takes over — no index.html there means 403, no fallback.
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";
}Redirect apex→www in both the :80 and :443 blocks; doing only :80 leaves HTTPS apex on a certificate error.
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;
}Check their presence by pasting the response into the header analyzer — no need to script it.
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;
Multi-stage builds, non-root, timezone — the three things that change image size and runtime behavior
Build deps stay in the builder stage; the final image only carries the static output plus nginx.
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;"]
Containers default to root; one USER line prevents host root after a container escape.
RUN addgroup -S app && adduser -S -G app app WORKDIR /app COPY --chown=app:app . . USER app
Copy manifests, install deps, then copy source — editing code no longer busts the dependency layer.
# 好:依赖层可复用 COPY package.json package-lock.json ./ RUN npm ci COPY . . # 坏:一行 COPY . . 会让任何代码改动都重装依赖 # COPY . . # RUN npm ci
Alpine defaults to UTC; log timestamps eight hours off local time is a common surprise.
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
ENV TZ=Asia/ShanghaiOrganized by "what did I just do". Figure out the mistake first, then pick the matching fix.
Save the commit on a new branch, then rewind the original. cherry-pick is safer than reset.
git branch feature-x # 在当前提交处建分支 git reset --hard origin/main # 原分支退回远端状态 git switch feature-x # 提交已经在 feature-x 上了
Reset commits live on in the reflog for a while. Find the entry and reset back.
git reflog # 找到 reset 之前的那条 HEAD@{n}
git reset --hard HEAD@{3} # 退回到那一刻
# 只想捞回某个文件:
git checkout HEAD@{3} -- path/to/fileTwo ways out: return to the pre-rebase state, or skip this commit and continue.
git rebase --abort # 完全放弃,回到 rebase 之前 git rebase --skip # 跳过当前这个冲突的提交,继续 git rebase --continue # 解决完冲突后继续
Rebase rewrites history so a plain push is rejected. Use --force-with-lease — it refuses if someone else pushed meanwhile.
git push --force-with-lease origin feature-x # 不要用 --force:它会把别人刚推的提交一起覆盖掉
Locally deleted branches can be recovered from reflog; if never pushed, act fast before gc.
git reflog # 找分支最后一次提交 git branch recovered-branch <commit-ish> # 用它重建分支
Stashes are never lost — list them all and pick the right one; order does not matter.
git stash list
git stash show -p stash@{2} # 先看内容确认是那一条
git stash pop stash@{2} # 恢复并从栈中删除
git stash apply stash@{2} # 恢复但保留在栈里(更保险)Rotate the key first. History rewriting only limits further spread — treat the leaked key as compromised.
# 1) 先去平台把该 key 作废并重新生成(这一步不能省) # 2) 再从历史里移除(需要强推,会改写所有协作者的克隆) git filter-repo --path .env --invert-paths # 或只从最新提交移除,后续提交补 .gitignore git rm --cached .env && echo ".env" >> .gitignore && git commit -m "chore: remove .env"
Choosing "theirs" during conflicts discards your work. Use reflog to return before the merge and redo it.
git reflog # 找 merge 之前的 HEAD
git reset --hard HEAD@{1} # 回到 merge 前
git merge --no-commit origin/x # 重新 merge,冲突逐个看清再解