← Back to list

ec2 優化調整

PHP 優化設定

James Liang · 2025-08-28 09:47 · 0 claps · 20.5 min read
#aws #optimization #ec2 #nginx #php
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

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_fulltrue 就該加大 memory_consumptionmax_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 4096worker_processes auto(t2.medium 通常 = 2),理論同時連線 ≈ 8192。 設 LimitNOFILE/worker_rlimit_nofile200k 很寬裕,之後還能增長。

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 ≥ Nginx keepalive_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_PREFIXSESSION_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_BODYCount 一小段時間觀察;或用 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 可能的缺點

誤快取個人化內容

  • 來源:帶 Cookielaravel_sessionXSRF-TOKENremember_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 的公共頁

  • 最保險:匿名頁路由走無狀態中介層(不使用 web middleware),不要觸發 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-storeprivate, 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