Serve static files with Sentinel including caching, compression, SPA support, and CDN-like features.
Use Case
- Serve static websites and assets
- Host Single Page Applications (SPAs)
- Provide CDN-like caching and compression
- Combine static files with API backend
Configuration
Create sentinel.kdl:
// Static Site Configuration
// High-performance static file serving
system {
worker-threads 0
graceful-shutdown-timeout-secs 30
}
listeners {
listener "https" {
address "0.0.0.0:8443"
protocol "https"
tls {
cert-file "/etc/sentinel/certs/site.crt"
key-file "/etc/sentinel/certs/site.key"
}
}
listener "http" {
address "0.0.0.0:8080"
protocol "http"
}
}
routes {
// Health check
route "health" {
priority 1000
matches {
path "/health"
}
service-type "builtin"
builtin-handler "health"
}
// Immutable assets (hashed filenames) - long cache
route "assets-immutable" {
priority 200
matches {
path-regex "^/assets/.*\\.[a-f0-9]{8}\\.(js|css|woff2?)$"
}
service-type "static"
static-files {
root "/var/www/site"
cache-control "public, max-age=31536000, immutable"
compress #true
}
}
// Regular assets - moderate cache
route "assets" {
priority 150
matches {
path-prefix "/assets/"
}
service-type "static"
static-files {
root "/var/www/site"
cache-control "public, max-age=86400"
compress #true
}
}
// Images and media - long cache
route "images" {
priority 150
matches {
path-prefix "/images/"
}
service-type "static"
static-files {
root "/var/www/site"
cache-control "public, max-age=604800"
}
}
// Favicon and robots
route "root-files" {
priority 100
matches {
path "/favicon.ico"
}
service-type "static"
static-files {
root "/var/www/site"
cache-control "public, max-age=86400"
}
}
route "robots" {
priority 100
matches {
path "/robots.txt"
}
service-type "static"
static-files {
root "/var/www/site"
cache-control "public, max-age=86400"
}
}
// SPA - catch-all with fallback to index.html
route "spa" {
priority 1
matches {
path-prefix "/"
method "GET"
}
service-type "static"
static-files {
root "/var/www/site"
index "index.html"
fallback "index.html" // SPA routing
cache-control "no-cache"
compress #true
}
policies {
response-headers {
set {
"X-Content-Type-Options" "nosniff"
"X-Frame-Options" "DENY"
"X-XSS-Protection" "1; mode=block"
"Referrer-Policy" "strict-origin-when-cross-origin"
}
}
}
}
}
observability {
metrics {
enabled #true
address "0.0.0.0:9090"
}
logging {
level "info"
format "json"
}
}
upstreams {
upstream "backend" {
target "127.0.0.1:3000"
}
}
Directory Structure
/var/www/site/
├── index.html
├── favicon.ico
├── robots.txt
├── assets/
│ ├── main.a1b2c3d4.js # Hashed (immutable)
│ ├── main.a1b2c3d4.css
│ ├── vendor.e5f6g7h8.js
│ └── fonts/
│ └── inter.woff2
└── images/
├── logo.png
└── hero.jpg
Setup
1. Create Site Directory
2. Deploy Your Site
# Example: Copy a built React/Vue/Next.js app
# Or a static site generator output
3. Run Sentinel
Testing
Basic Request
Expected response:
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Immutable Asset Caching
Expected:
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: public, max-age=31536000, immutable
Content-Encoding: gzip
SPA Routing
# Direct page access
# Returns index.html (fallback for SPA routing)
Compression
Check for Content-Encoding: gzip or Content-Encoding: br.
Static Site with API Backend
Combine static file serving with API proxying:
routes {
// API routes - proxy to backend
route "api" {
priority 500
matches {
path-prefix "/api/"
}
upstream "api-backend"
service-type "api"
}
// WebSocket for real-time features
route "ws" {
priority 400
matches {
path "/ws"
}
upstream "api-backend"
}
// Static files - SPA
route "spa" {
priority 1
matches {
path-prefix "/"
}
service-type "static"
static-files {
root "/var/www/site"
fallback "index.html"
}
}
}
upstreams {
upstream "api-backend" {
target "127.0.0.1:3000"
}
}
Multi-Site Hosting
Host multiple sites on the same Sentinel instance:
routes {
// Site A
route "site-a" {
priority 100
matches {
host "site-a.example.com"
path-prefix "/"
}
service-type "static"
static-files {
root "/var/www/site-a"
fallback "index.html"
}
}
// Site B
route "site-b" {
priority 100
matches {
host "site-b.example.com"
path-prefix "/"
}
service-type "static"
static-files {
root "/var/www/site-b"
fallback "index.html"
}
}
// Default site
route "default" {
priority 1
matches {
path-prefix "/"
}
service-type "static"
static-files {
root "/var/www/default"
}
}
}
Customizations
Directory Listing
static-files {
root "/var/www/files"
directory-listing #true
}
Custom 404 Page
route "spa" {
service-type "static"
static-files {
root "/var/www/site"
fallback "404.html"
}
error-pages {
pages {
"404" {
format "html"
template "/var/www/site/404.html"
}
}
}
}
Content Security Policy
route "spa" {
policies {
response-headers {
set {
"Content-Security-Policy" "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
}
}
}
}
Brotli Compression
static-files {
root "/var/www/site"
compress #true
compression-level 6
compression-types "text/html" "text/css" "application/javascript" "application/json"
}
Performance Tips
- Use hashed filenames for assets to enable immutable caching
- Precompress files (
.gz,.br) for faster response times - Separate routes for different cache policies
- Enable HTTP/2 for multiplexing
- Use CDN in front of Sentinel for global distribution
Next Steps
- API Gateway - Add backend APIs
- Security - Add WAF protection
- Observability - Monitor traffic