ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJV+SFaaSS80QR0mZO5369wV+Hmvffzinbq6OWZBju3iY/Jacj/CoEmqUIaO3pNwZPs+FJkwaSlYhYiYCUrVkkA6CwqmjBinpcwPeg9hjLA1dSkyDnaNl0DqXfq6L+xag23WAD/YV+spoAs3QHC2re9WSPf7NJ+7XWK1JXIhZv2xkW4F3ss1oOEZfYtZFI3xP32CjJHFi9A1DiKjDGz7JQcCPKlqHcA0TQag9EntE1Oc50l1rKsUSh9j82mxU30IS2UL1wUS9FZWuAu+7SF/KqyjHjH0+W/UIGDwcgUh87LTgwEBFrcbNJC2yKPsLeuYZrWMaLl7nst0144hGgLWNp ssh-key-2026-01-12
compartment
ocid1.tenancy.oc1..aaaaaaaarzc3njhhyh2wro6dtbnyru3ssama2r3uf3mdufd7owup3inhs52q
|
|---|
https://idcs-27e4092fa5e143949c5ec777a9513977.identity.oraclecloud.com:443
user nginx; worker_processes auto; # 1 worker per CPU core worker_rlimit_nofile 65535; # match system ulimit
error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;
events { worker_connections 4096; # per worker; total = workers × 4096 use epoll; # Linux: most efficient I/O model multi_accept on; # accept all new connections at once }
http {
# ── MIME / Encoding ──────────────────────────────────────────────────────
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
charset_types text/plain text/css application/json
application/javascript text/xml application/xml
application/xml+rss text/javascript;
# ── Logging ──────────────────────────────────────────────────────────────
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log main buffer=64k flush=5s;
# ── Transport Optimisation ───────────────────────────────────────────────
sendfile on;
tcp_nopush on; # batch TCP packets (pairs with sendfile)
tcp_nodelay on; # low-latency for keep-alive connections
keepalive_timeout 75s;
keepalive_requests 1000;
# ── Compression (Gzip) ───────────────────────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # sweet-spot: compression vs CPU
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256; # don't compress tiny responses
gzip_types
text/plain
text/css
text/javascript
text/xml
text/x-component
application/json
application/javascript
application/x-javascript
application/xml
application/xhtml+xml
application/rss+xml
application/atom+xml
application/vnd.ms-fontobject
image/svg+xml
image/x-icon
font/truetype
font/opentype;
# ── Brotli (requires ngx_brotli module) ─────────────────────────────────
# brotli on;
# brotli_comp_level 6;
# brotli_types text/plain text/css application/json application/javascript
# image/svg+xml font/opentype font/truetype;
# ── Buffer / Timeout Tuning ──────────────────────────────────────────────
client_max_body_size 50m;
client_body_buffer_size 128k;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
# ── Proxy Buffering (used for API upstream) ───────────────────────────────
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# ── Open File Cache ───────────────────────────────────────────────────────
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# ── Rate Limiting Zones ──────────────────────────────────────────────────
# General API zone: 30 req/s per IP
limit_req_zone $binary_remote_addr zone=api_limit:20m rate=30r/s;
# Auth endpoints: strict 5 req/s per IP
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/s;
# Connection limit zone
limit_conn_zone $binary_remote_addr zone=conn_limit:20m;
# ── Upstream: .NET Web API ────────────────────────────────────────────────
upstream dotnet_api {
least_conn; # route to least-busy instance
server 127.0.0.1:5000 weight=1;
# server 127.0.0.1:5001 weight=1; # add more instances for scale-out
keepalive 64; # persistent connections to upstream
keepalive_requests 1000;
keepalive_timeout 75s;
}
# ── Security: hide server info ────────────────────────────────────────────
server_tokens off;
# ── Map: Cacheable static extensions ─────────────────────────────────────
map $uri $static_cache_control {
default "";
~*\.(js|css|woff2?|ttf|eot|otf)$ "public, max-age=31536000, immutable";
~*\.(png|jpg|jpeg|gif|ico|webp|avif|svg)$ "public, max-age=2592000";
~*\.(html)$ "no-cache";
}
# =========================================================================
# HTTP → HTTPS redirect
# =========================================================================
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Allow ACME challenge for Let's Encrypt / cert-manager
location /.well-known/acme-challenge/ {
root /var/www/certbot;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
# =========================================================================
# HTTPS — Main Application
# =========================================================================
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com; # ← change this
root /var/www/app/react/build; # ← React production build path
index index.html;
# ── TLS ───────────────────────────────────────────────────────────────
ssl_certificate /etc/ssl/certs/yourdomain.crt; # ← change this
ssl_certificate_key /etc/ssl/private/yourdomain.key; # ← change this
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:
DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:50m; # ~200 000 sessions
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling — reduces TLS handshake latency
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# HSTS — enforce HTTPS for 1 year (enable after confirming TLS works)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# ── Security Headers ─────────────────────────────────────────────────
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.jsdelivr.net; "
"style-src 'self' 'unsafe-inline' fonts.googleapis.com; "
"font-src 'self' fonts.gstatic.com data:; "
"img-src 'self' data: blob: https:; "
"connect-src 'self' https://api.yourdomain.com; "
"frame-ancestors 'self';"
always;
# ── Rate Limit for whole server ───────────────────────────────────────
limit_conn conn_limit 100;
# ── API Reverse Proxy → .NET Web API ─────────────────────────────────
location /api/ {
limit_req zone=api_limit burst=60 nodelay;
limit_conn conn_limit 50;
proxy_pass http://dotnet_api;
proxy_http_version 1.1;
# Required for upstream keep-alive
proxy_set_header Connection "";
# Forward real client info
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_set_header X-Forwarded-Host $host;
# Hide upstream headers that leak server details
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# Timeouts
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Disable caching for API responses (use Cache-Control from .NET)
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# ── Auth Endpoints (stricter rate limiting) ───────────────────────────
location ~ ^/api/(auth|login|register|token)/ {
limit_req zone=auth_limit burst=10 nodelay;
limit_conn conn_limit 20;
proxy_pass http://dotnet_api;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
# ── SignalR / WebSockets ──────────────────────────────────────────────
location /hubs/ {
proxy_pass http://dotnet_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 3600s; # keep long-lived WS connections alive
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
}
# ── Health Check (no logging, no auth needed) ─────────────────────────
location = /health {
proxy_pass http://dotnet_api/health;
access_log off;
proxy_set_header Host $host;
}
# ── React Static Assets (hashed filenames = immutable cache) ──────────
location /static/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Vary "Accept-Encoding";
access_log off;
# Serve pre-compressed if available
gzip_static on;
# brotli_static on; # enable with ngx_brotli
}
# ── Media / Font assets ───────────────────────────────────────────────
location ~* \.(woff2?|ttf|eot|otf)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
add_header Access-Control-Allow-Origin "*";
access_log off;
}
location ~* \.(png|jpg|jpeg|gif|ico|webp|avif|svg)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# ── React SPA fallback (client-side routing) ──────────────────────────
location / {
try_files $uri $uri/ /index.html;
# index.html must never be cached by the browser
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
# ── Block common exploit probes ───────────────────────────────────────
location ~* \.(php|asp|aspx|jsp|cgi|env|git|svn)$ {
deny all;
return 404;
}
location ~ /\. {
deny all;
return 404;
}
# ── Custom Error Pages ────────────────────────────────────────────────
error_page 404 /index.html; # SPA handles its own 404s
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
# =========================================================================
# Optional: Separate API subdomain (api.yourdomain.com)
# Uncomment and adjust if you prefer subdomain routing over path routing
# =========================================================================
# server {
# listen 443 ssl http2;
# server_name api.yourdomain.com;
#
# ssl_certificate /etc/ssl/certs/yourdomain.crt;
# ssl_certificate_key /etc/ssl/private/yourdomain.key;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
#
# add_header Strict-Transport-Security "max-age=31536000" always;
# add_header X-Content-Type-Options "nosniff" always;
#
# location / {
# limit_req zone=api_limit burst=60 nodelay;
# proxy_pass http://dotnet_api;
# proxy_http_version 1.1;
# proxy_set_header Connection "";
# 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;
# }
# }
} # end http