ec2 優化調整
PHP 優化設定
ec2 優化調整
PHP 優化設定
php.ini
1. 記憶體管理
memory_limit = 256M
- Laravel 一般請求大概用不到太多記憶體,但有些 Queue Job 可能比較重。
- 建議設在
256M(避免因為太小導致 OOM,太大會吃光記憶體)。
2. 執行時間 & 請求處理
max_execution_time = 60
max_input_time = 60
- API 類型的應用,請求超過 60 秒通常代表設計問題。
- Queue Job 不受這個限制(Laravel Horizon 會控制)。
post_max_size = 32M
upload_max_filesize = 32M
- 看你應用需求調整,如果有大檔上傳,可以再往上加。
3. Realpath Cache
realpath_cache_size = 4096k
realpath_cache_ttl = 600
- Laravel 使用大量的
include/require,適度加大 cache 可減少檔案系統 I/O。
/etc/php/8.3/cli/conf.d/10-opcache.ini
OPcache(最重要的效能調整)
; configuration for php opcache module
; priority=10
zend_extension=opcache.so
; --- Enable ---
opcache.enable=1
opcache.enable_cli=1
; --- Capacity ---
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=80000
; --- File validation (prod) ---
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.file_update_protection=0
opcache.use_cwd=1
opcache.revalidate_path=0
; --- Compatibility ---
opcache.save_comments=1
opcache.enable_file_override=1
; --- Hygiene ---
opcache.max_wasted_percentage=5
; --- Logging (quiet in prod) ---
opcache.log_verbosity_level=1
opcache.record_warnings=0
; --- JIT (off for web workloads) ---
opcache.jit=0
opcache.jit_buffer_size=0
opcache 觀察健康度(命中率、是否滿倉):
php -r 'var_export(opcache_get_status(false));'
- 命中率(
opcache_hit_rate):長期 > 95% 很不錯;若常 < 90%,要看是不是validate_timestamps=1的開發環境或快取太小。 - 是否滿倉(
cache_full):true就該加大memory_consumption或max_accelerated_files。 - 浪費率(
current_wasted_percentage): > 5~10%(且攀升)表示需要重載或加大容量。
在程式中監控
public function getOpcacheStatus()
{
abort_unless(auth()->user()->is_super_admin, 403, 'Unauthorized');
return response()->json(opcache_get_status(false));
}
/etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
- 計算公式:
max_children ≈ (總RAM - 系統 & DB) ÷ 單一 PHP 請求平均消耗在 4GB RAM 的機器,扣掉 OS+Nginx+MySQL,大概能留 2.5GB 給 PHP。 假設單個 PHP 請求吃 120MB → 20 個 children 差不多。 pm.max_requests = 500→ 避免 memory leak 長時間堆積。
sudo systemctl restart php8.3-fpm
nginx
/etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/modules-enabled/*.conf;
worker_rlimit_nofile 200000;
events {
worker_connections 4096; # ↑ 提升併發能力(t2.medium 建議值)
use epoll;
multi_accept on;
}
http {
##
# 基本參數
##
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
# 連線維持(降低重複握手成本)
keepalive_timeout 70s;
keepalive_requests 1000;
# 名稱/型態表
types_hash_max_size 4096;
##
# TLS(只留安全協定)
##
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
##
# 日誌
##
access_log /var/log/nginx/access.log combined buffer=32k;
error_log /var/log/nginx/error.log warn;
##
# FastCGI / Proxy 通用(較保守,站台再精準放寬)
##
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
client_max_body_size 8M; # ↑ 依你的上傳需求調整
proxy_connect_timeout 10s; # ↓ 不要全域放太大
proxy_send_timeout 60s;
proxy_read_timeout 90s;
##
# 壓縮
##
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/woff2;
##
# 開檔快取(大量小檔很有用)
##
open_file_cache max=200000 inactive=60s;
open_file_cache_valid 120s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
map $status $static_cache_control {
200 "public, max-age=2592000, immutable";
default "no-cache, no-store, must-revalidate";
}
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
提高 systemd 的檔案上限
sudo systemctl edit nginx
輸入以下內容並儲存(會建立 /etc/systemd/system/nginx.service.d/override.conf):
[Service]
LimitNOFILE=200000
套用:
sudo systemctl daemon-reload
sudo systemctl restart nginx
在 nginx.conf 宣告 rlimit
在 nginx.conf 的最上層(非 http/events/server 區塊)加入:
worker_rlimit_nofile 200000;
這會把 worker processes 的檔案上限同步拉高(配合上面的 systemd 限制)。
檢查現況
重啟後檢查實際限制值:
pid=$(pidof nginx | awk '{print $1}')
cat /proc/$pid/limits | grep "open files"
# 或看 worker(非 master):
for p in $(pgrep -f "nginx: worker process"); do cat /proc/$p/limits | grep "open files"; done
看到 Max open files 提升到你設定的數字(例如 200000)就 OK。
***worker_connections 與實際上限的關係***
- 最大連線數大約是:
max connections ≈ worker_processes × worker_connections - 每個連線可能佔用 1~2 個 FD(含 upstream/快取/檔案等),所以 OS 的 FD 上限要比理論值高一截。
- 你目前
worker_connections 4096、worker_processes auto(t2.medium 通常 = 2),理論同時連線 ≈ 8192。 設LimitNOFILE/worker_rlimit_nofile為 200k 很寬裕,之後還能增長。
nginx 站台設定
server {
server_name demo.today;
root /var/www/demo/public;
index index.php;
charset utf-8;
# ---- 安全標頭(基礎款,低風險)----
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Access-Control-Allow-Origin "$cors_origin" always;
location ^~ /livewire/ {
# 重要:不要當純靜態處理,不要長快取
expires off;
add_header Cache-Control "no-cache, no-store, must-revalidate";
try_files $uri /index.php?$query_string;
}
# ---- 靜態資源(由 Nginx 直出 + 長秒快取)----
location ~* \.(?:jpg|jpeg|gif|png|webp|ico|svg|css|js|woff2?|ttf|eot)$ {
expires 30d;
add_header Cache-Control $static_cache_control always;
access_log off;
# 若你希望「不存在就交回 PHP 嘗試」→ 用這行(較溫和)
# try_files $uri /index.php?$query_string;
# 若你確定這些都是純靜態、不存在就真的 404 → 也可以保留這行:
try_files $uri =404;
}
# ---- Laravel 前端路由 ----
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
# ---- PHP(FastCGI)----
location ~ \.php$ {
# 如果改用 TCP,記得把 upstream 做 keepalive;現在走 unix socket OK
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
include fastcgi_params;
# 正確傳遞檔案與 root,避免 path 解析不一致
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_hide_header X-Powered-By;
# 開啟/調整 FastCGI 緩衝,降低 php-fpm 壓力
fastcgi_buffering on;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 64k;
fastcgi_temp_file_write_size 64k;
fastcgi_request_buffering on; # 大型 POST 先全收再丟給 php-fpm
# 連線與讀取逾時(一般 60s 夠用;報表頁面再個別放寬)
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
# 持久連線(配合 upstream keepalive;走 socket 也可保留)
fastcgi_keep_conn on;
# 上傳限制(備援,通常以 client_max_body_size 為準)
client_max_body_size 8m;
}
# ---- 禁止存取隱藏檔(.env 等)----
location ~ /\.(?!well-known).* {
deny all;
}
}
sudo nginx -t && sudo systemctl reload nginx
服務層(Nginx / ALB / TLS)
ALB ↔ Nginx 逾時對齊
經驗法則:*ALB
idle_timeout≥ Nginxkeepalive_timeout≥ 上游 `proxy__timeout`**,彼此錯開 15–30 秒以上,避免「上游還等、前面先斷」或反之。
ALB
idle_timeout: 120s
Nginx:
keepalive_timeout 70s;
proxy_read_timeout 90s;
proxy_send_timeout 60s;
若有長任務路徑,單獨在該 location 加長 proxy_read_timeout / fastcgi_read_timeout。
範例
location ^~ /reports/export {
proxy_read_timeout 300s;
fastcgi_read_timeout 300s;
add_header X-Accel-Buffering no;
}
後續觀察與可能優化方向
快取與條件式回應
- Rate limit(防爆裂流量,API 尤其有感)
limit_req_zone $binary_remote_addr zone=api_rate:10m rate=10r/s;
location /api/ { limit_req zone=api_rate burst=20 nodelay; }
FPM 狀態與慢查請求
- 在 pool
www.conf啟用:
pm.status_path = /fpm-status
request_terminate_timeout = 60s ; 防止單請求拖太久
slowlog = /var/log/php8.3-fpm.slow.log
request_slowlog_timeout = 3s
- 在 Nginx 加只允許內網/管理 IP 存取
/fpm-status,方便診斷。
部署自動化
應用層(Laravel)
快取與 Session
- 你已接 ElastiCache:再補 Session Lock 參數(避免同一使用者併發競態)。
- 設定
CACHE_PREFIX與SESSION_COOKIE明確命名,便於觀測清理。
任務與併發
- 使用 批次排程與 資料庫索引(觀察最慢查詢 → 補 index)。
- Queue 用 延遲 / 批次 降峰值;Horizon 儀表板設定佇列分池(慢任務與即時任務分離)。
檔案上傳
- 直接上傳至 S3(Presigned URL),後端只收回報,能大幅降低 EC2/ALB 負擔。
- Livewire/前端亦可改走 S3 預簽名,WAF 誤擋機率趨近 0。
資產最佳化
- Vite 啟用 分包與 壓縮,圖片轉 WebP/AVIF。
- 回應加
Content-Encoding(由 Nginx 壓縮),並確保immutable。
資料庫 / Redis
DB 連線池/重試
- 若用 MySQL:確保
wait_timeout與 Laravel 逾時一致;開read/write分離時加上 retry/backoff。 - Postgres 可考慮 pgBouncer(連線池顯著減負)。
ElastiCache(Redis)
- 參數組:
maxmemory-policy=allkeys-lru(若混用快取)或volatile-lru(純 session)。 - 觀測:
used_memory,evicted_keys,latency,超標就擴容或調 TTL。
前置層(CDN / WAF / 日誌)
CloudFront(建議加在最前面)
- 大幅 offload 靜態資源與公開下載。
- 原生 Brotli、全球邊緣;WAF 也可綁在 CloudFront 層。
WAF
- 建議把
SizeRestrictions_BODY設 Count 一小段時間觀察;或用 Scope-down,其餘路徑維持 Block。 - 開 Logging 到 CloudWatch Logs,做常見規則儀表板(SQLi/XSS 命中率)。
日誌輪替與壓縮
logrotate
設定檔位置:/etc/logrotate.d
nginx 跟 php8.3-fpm 系統已經預設
系統/資源
nofile 上限
- 已調
LimitNOFILE+worker_rlimit_nofile;定期cat /proc/$pid/limits檢查是否生效。
監控
- CloudWatch Agent:CPU、Mem、Disk I/O、網路、File Descriptors。
- Nginx
stub_status(限內網):併發、等待、讀寫。 - APM(Datadog/NewRelic/黑箱選一):P95/P99、DB 慢查、外呼延遲。
匿名頁 micro-cache 可能的缺點
誤快取個人化內容
- 來源:帶
Cookie(laravel_session、XSRF-TOKEN、remember_web等)、Authorization、A/B 測試或地區/語系 Cookie。 - 風險:把某位使用者的頁面回給所有人(外觀或資料錯亂)。
快取 Set-Cookie(洩漏/干擾登入流程)
- 來源:Blade 內用了
csrf_token()、session()、old()等導致匿名頁也發Set-Cookie。 - 風險:把某人的 Session/Csrf Cookie 當作公共回應發給別人(或讓別人的登入流程怪怪的)。
內容延遲更新(短暫不一致)
- 來源:TTL 內(例如 10~60 秒)即便資料已更新,使用者還看到舊頁。
- 影響:內容營運、剛發文/改價時會覺得「怎麼沒更新」。
快取錯誤頁/重導頁
- 來源:未限制
cache_valid,把 500/502 也快取住;或把一次性 302 快取了。 - 影響:短時間大量使用者都吃到錯誤/重導。
國際化/裝置差異被混淆
- 來源:語系、貨幣、行動版/桌機版用 Cookie 或 UA 判斷,但快取 key 沒分流。
- 影響:顯示錯語言、錯幣別、錯版型。
Cache Poisoning / 意外的查詢參數
- 來源:把帶奇怪 query 的頁面(
?x=…)也當作可快取;或回應沒有好好設定Cache-Control。 - 影響:惡意參數導致快取被污染,或回錯版本。
磁碟/IO 壓力與清理
- 來源:keys 區太小或 inactive 過長,磁碟滿、inode 多。
- 影響:回源暴增、快取命中率下降。
安全實作範例(Laravel + Nginx FastCGI)
1) 只快取「真正匿名 + GET」
# 任何帶這些跡象就跳過快取
map "$request_method|$http_authorization|$http_cookie" $skip_cache {
default 0;
"~^(?!GET)\|" 1; # 非 GET
"~^GET\|.+\|" 1; # 有 Authorization
"~^GET\|\|.*(laravel_session|XSRF-TOKEN|remember_web)=" 1; # 有 Cookie
}
fastcgi_cache_path /var/cache/nginx/fcgi levels=1:2 keys_zone=FCGI:50m inactive=10m max_size=5g;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
# 開 cache(僅匿名)
fastcgi_cache FCGI;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# 只快取健康回應(避免 500/502/504)
fastcgi_cache_valid 200 301 302 10s; # micro-cache 的 TTL,從 5~30s 起
fastcgi_cache_valid 404 10s;
# 不對 500/502/503/504 設定 valid → 預設不快取
# 防止洪峰穿透/更新時回舊頁
fastcgi_cache_lock on;
fastcgi_cache_use_stale updating error timeout http_500 http_503;
add_header X-Cache-Status $upstream_cache_status always;
}
2) 避免快取 Set-Cookie 的公共頁
- 最保險:匿名頁路由走無狀態中介層(不使用
webmiddleware),不要觸發 Session。 - 或在 Nginx 僅對特定「確定不該設 Cookie」的路徑忽略
Set-Cookie(小心使用):
# 僅在 /news/ 這種純公開內容可考慮
location ^~ /news/ {
fastcgi_ignore_headers Set-Cookie;
}
3) 由應用層宣示可快取
- 對匿名公共頁送:
Cache-Control: public, max-age=10, stale-while-revalidate=60 - 對個人化/需要即時性的頁送:
Cache-Control: no-store或private, no-cache
4) 語系/裝置分流
- 語系走 URL(
/zh-TW/*)、子域或 query,而不是 Cookie;或把語系 token 放進fastcgi_cache_key。 - 行動/桌機請用 RWD,避免依 UA 產兩份頁(除非確有必要)。
5) 管理/預覽一律跳過
- 後台人員常抱怨「改了沒更新」。對
/admin/*、登入者、或帶preview=1的請求強制no-cache/跳過快取。 - 若真的要「立即生效」可加簡易 purge(Nginx 需加模組,或讓後端改版號/加查詢參數)。
什麼時候不建議開 micro-cache?
- 頁面高度個人化(購物車、庫存即時變動、價格因人而異)。
- 登入比例極高、匿名流量很少(命中率低、收益不大)。
- 內容更新需要「秒級一致性」,而且營運不能接受 10 秒延遲。
什麼時候特別值得開?
- 熱門「公開內容」:列表、文章、產品詳情(無個人化)。
- 有外部 API/DB 峰值時,想用 5~30 秒的緩衝「削峰填谷」。
- 前面還有 CDN,但源站在尖峰會被打爆:源站 micro-cache 可再擋一層。
메타데이터
- post_id
- d7936d869ee8
- slug
- ec2-優化調整-d7936d869ee8
- url
- https://medium.com/@lalalili/ec2-%E5%84%AA%E5%8C%96%E8%AA%BF%E6%95%B4-d7936d869ee8
- canonical_url
- https://medium.com/@lalalili/ec2-%E5%84%AA%E5%8C%96%E8%AA%BF%E6%95%B4-d7936d869ee8
- author_url
- https://medium.com/@lalalili
- status
- ok
- fetched_at
- 2026-07-17 22:16:43