UWAS
Unified Web Application Server
One binary to serve them all.
Apache + Nginx + Varnish + Caddy + cPanel → UWAS
Note: UWAS is intended for production use; verify it against your own deployment, traffic, and security requirements.
What is UWAS?
UWAS replaces your entire web server stack and hosting control panel with a single Go binary. Auto HTTPS, built-in caching, PHP support, .htaccess compatibility, reverse proxy, WebSocket forwarding, WAF, multi-user access control, and a 42-page React dashboard backed by 254 explicit admin API route registrations.
One binary. Zero hassle.
Current Snapshot (v0.11.41)
- Dashboard pages: 42 (
web/dashboard/src/pages;settingsSections.tsxlives there too but is section definitions, not a page) - Admin API routes: 254+ explicit route registrations in
internal/admin/routes.gounder/api/v1plus dashboard/static handlers - Go packages: 71 (
go list ./...) — 63 underinternal/, 2 underpkg/; 57 carry tests - CLI commands: 19
- Test status: all gates pass —
go build,go vet,staticcheck,go test,go test -race, dashboard npm build; CI runs additionalgovulncheck, shellcheck, installer tests, Docker Compose validation, and docs/site builds - Security/stability fixes: v0.11.29 hardens against request floods (async logging, rate-limited redirects, cache bypass fix) and moves to Go 1.26.9 for HTTP/2 DoS fixes
- Security posture: risk score 2.1/10 (Low) per July 2026 reassessment
- Access-control bypasses closed: a trailing
/(/private/a.txt/,//,/./) no longer skips$-anchored regex locations such asbasic_auth;/wp-config.old/.,/dump.sql/.and/img/a.jpg/.no longer bypass the wp-config copy guard, WAF file-probe rules or hotlink guard - Clone/migrate: a failed DB clone no longer leaves the target
wp-config.phpon the production database, failed rsync/copy stops the migration, partial copies are discarded - Backups and cleanup: a failing
mysqldumpno longer lets a files-only backup prune the older backup holding the database; domain delete withcleanup=truereports cron/SFTP cleanup failures;wordpress.Hardenre-checks themu-pluginssymlink - Dashboard: password fields reset when the form moves to another row; release builds report
build.Date
- Tenant isolation: PHP workers (php-cgi) and wp-cli no longer run as root, root-owned writes and chowns on tenant paths refuse symlinks (backup restore, WordPress install/update, image optimizer, clone/migrate), bootstrap rejects DNS-rebinding hosts
- Availability: a NUL byte in a WordPress install request no longer crashes the server,
domains.dandinclude:domains no longer produce a duplicate host that blocks restart, a second instance no longer clobbers the PID file,uwas stopwaits for graceful shutdown - Disclosure: PHP variant sources (
.phtmland friends), Composervendor/,node_modules/and lock files are no longer served, cache no longer leaks template headers, config export redacts secrets - Destructive actions: DB import and API-key regeneration require PIN / current password; many API status codes, null lists and dashboard load-error states fixed
- Cache and DoS hardening: bounded
Accept-Encodingcache keys, no cache entries for unmatched hosts, bounded reads, image-conversion andsystemctldeadlines, oversized app logs rotated at start, deploy output cap
- 2FA fixes: the per-request gate no longer rejects the second admin request in a 30 s window, the web terminal and SSE stats work with 2FA, the dashboard recovers from an expired code and sends it on every call
- PHP sites no longer cache dynamic output by URL extension (
/index.php/app.js, front-controller/invoice/123.pdf) - Bot guard no longer treats proxied requests from the same host as local; IPv6 login lockout by /64
- Cache hits honour
Range/If-Modified-Since; PIN required for package removal and software restore/backup delete; curl 8.23 floor in PHP images (CVE-2026-92392)
- Evidence-audit rounds 164–170: WordPress install no longer deletes a document root named
wordpress, rewrite/redirect/htaccess fixes, alert e-mail headers and SMTPS, MX priority for Hetzner/Route 53/Cloudflare, audit log coverage for mutating routes, HTTP/3Alt-Svconly when the UDP listener bound, second shutdown signal terminates,[]instead ofnullin API lists, dashboard load-error and stale-state fixes
- Evidence-audit rounds 156–163: clone endpoint no longer hangs the panel, config-write failures reported as 500 across domain/cloudflare/PHP/cert flows, circuit-breaker probe leak, own-password change requires the current password
- Evidence-audit rounds 152–155: PHP uninstall/keyring hardening,
${VAR:-x}empty fallback, proxyTE: trailers,Retry-Afterrounding, SFTP/backup/ACME/logging fixes
- PHP update / uninstall from the dashboard, API and CLI; vulnerable versions get an "Update now" action
- Evidence-audit rounds 147–151: child-process env secrets filter, monitor proxy SSRF, firewall/deploy/autoblock ownership fixes
- PHP php2shell RCE (CVE-2026-109618/109619): Docker images on PHP 8.3.36; dashboard flags vulnerable and EOL PHP; PHP 8.2+ only
- Go 1.26.9 + x/net v0.60.0: HTTP/2 DoS vulnerabilities fixed
- Logging never blocks requests (flood no longer ends in an OOM kill)
- Canonical redirects and global 429s feed rate limiting / autoblock
- Cache: client no-cache ignored, origin max-age honoured, alias purge, rule cache_control on proxies
- Published binaries for the v0.11.27 product changes (that tag failed lint)
- Image conversions publish atomically under concurrency
- Domain backup fails closed if the database dump fails
- Dashboard tests drop
anyso releasenpm run lintstays green
- WAF blocks scanner probes (
phpinfo.php,secrets.json,.bak) when WAF is on - Alert titles spell out 5xx spikes instead of
error_spike - (tag only — binaries in v0.11.28)
- Round-robin uses configured backend weights; circuit probes recover
- SQL explorer refuses
EXPLAIN ANALYZE; Docker short flags stay off the allowlist - DNS record ids are escaped; nested zones match the longest name
- Published binaries for the v0.11.24 changes; dashboard lockfile clears
brace-expansion and undici advisories
v0.11.24 highlights:
- File Manager: page through every file (50 per page); new uploads/saves
- Cache keys are length-prefixed (one miss cycle); FastCGI responses cap
v0.11.23 highlights:
- Deploy: listen probe 45s (was 3s) — stops false rollbacks on slow Node boots;
hostname · IP
v0.11.22 highlights:
- Apps: Environment vars keep operator save order; full-width env editor;
v0.11.21 highlights:
- Deploy: clear stale
.git/*.lock, live SSE deploy logs, 5m UI timeout;
&&)
v0.11.20 highlights:
- Firewall: hide
# uwas-autoblockrows from the rules table; enable opens
v0.11.19 highlights:
- Firewall: heal missing IPv4 default DENY (v6-only orphan no longer blocks restore)
- Firewall: ↑↓ move no longer drops the bottom DENY; empty protocol shows as
any; panel/API reject blank port+source allow/deny
v0.11.17 highlights:
- Firewall: visible rule count matches the table (IPv6 hidden noted); empty port
v0.11.16 highlights:
- WAF body scanning covers
application/jsonandmultipart/form-data - Firewall source IP/CIDR, allow-above-deny insert, arrow reorder
- Database user Drop; remote MySQL firewall prompt for 3306
- Deploy-key Generate on app Edit
- TLS handshake allowlist is now lock-free (atomic pointer instead of mutex + linear scan)
- IPACL / GeoIP / CORS / WAF middleware run as predicates on the hot path (no per-request wrapper allocation)
- API-key authentication is O(1) (secondary hash index alongside the username map)
- Cache LRU promotion debounced; per-host mutex on access-log writes; ACME renewal split from cert-map iteration
- New
internal/respondpackage centralizes JSON responses with operator-visible 5xx logging - Per-handler latency histogram (
uwas_handler_duration_seconds{handler,quantile}) - Behavioural change: plaintext
api_keyfallback is now opt-in (global.users.allow_legacy_plaintext_api_key, defaultfalse). Operators upgrading from v0.4.x with a plaintext key must set the flag or migrate to hashed credentials.
Features
Web Server
- Auto HTTPS — Let's Encrypt certificates with zero configuration
- HTTP/3 (QUIC) — Via quic-go with Alt-Svc header advertisement
- Built-in Cache — Varnish-level caching with grace mode, tag-based purge, ESI (Edge Side Includes)
- PHP Ready — FastCGI with connection pooling and .htaccess support
- Per-domain PHP — Multiple PHP versions per domain with auto-port assignment, crash auto-restart
- Load Balancer — 6 algorithms (round-robin, least-conn, ip-hash, uri-hash, random, sticky), health checks, circuit breaker, canary routing
- WebSocket Proxy — Transparent TCP tunnel with hijack + bidirectional pipe
- URL Rewrite — Apache mod_rewrite compatible engine
- Brotli + Gzip — Dual compression with Accept-Encoding negotiation
- Image Optimization — On-the-fly WebP/AVIF conversion
Hosting Control Panel
- 42-page Dashboard — React 19 admin panel with dark/light theme
- Applications — Deploy and supervise Node.js, Python, Ruby, Go, custom, and Docker apps
- Web Terminal — Browser-based shell via WebSocket-to-PTY bridge
- Multi-user Auth — Admin, reseller, user roles with TOTP 2FA
- WordPress Management — One-click install, plugin updates, debug mode, error log viewer
- DNS Zone Editor — Full CRUD for Cloudflare, Hetzner, DigitalOcean, Route53
- File Manager — Browse, edit, upload, delete files via dashboard
- SFTP Server — Built-in pure Go SFTP with chroot per domain + SSH key management
- Database Management — MySQL/MariaDB + Docker containers (MariaDB/MySQL/PostgreSQL)
- Site Migration — SSH wizard: rsync files + dump/import database
- Clone/Staging — One-click domain clone with DB duplication
- Backup/Restore — Local, S3, SFTP providers with scheduling + webhook notifications
- Cron Jobs — Per-domain cron management with execution monitoring and failure alerts
Security & Monitoring
- WAF — SQL injection, XSS, shell, RCE detection
- Per-domain Rate Limiting — Sharded token bucket per domain
- Bandwidth Limits — Monthly/daily caps with throttle or block action
- Webhook Events — 12 event types plus a
testevent, HMAC-SHA256 signed, with retry - Uptime Monitoring — Per-domain health checks with alerting
- Analytics — Per-domain traffic, referrer tracking, user agent breakdown
- Prometheus Metrics — p50/p95/p99 latency percentiles
- Audit Logging — Track all admin actions with timestamps and IPs
- IP Access Control — Per-domain whitelist/blacklist
- Resource Limits — Per-domain CPU/memory/PID limits via Linux cgroups v2
DevOps
- Git Deploy — Git clone/pull + build + health check + restart with concurrent protection, env persistence, and cancellation support
- AI-Native — MCP server for LLM-driven management
- Nginx/Apache Migration — CLI config converter
- Hot-Reload — All per-domain chains rebuild on SIGHUP (zero downtime)
- Self-Update — Binary auto-update from GitHub releases
- CI/CD — GitHub Actions for build, test, release automation (manual or on
v*release tags, not on every push — see CONTRIBUTING.md) - Single Binary — ~16MB, no runtime dependencies, just download and run
Install
# One-line install (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/uwaserver/uwas/main/install.sh | sh
Update
# One-line update (detects current version, downloads latest, restarts service)
curl -fsSL https://raw.githubusercontent.com/uwaserver/uwas/main/update.sh | sh
Or download from GitHub Releases.
Upgrading? Releases that change runtime behavior are documented in
UPGRADING.md. v0.8.7 (security release) tightens several
defaults — review it before upgrading multi-user or docker-compose deployments.
Build from Source
git clone https://github.com/uwaserver/uwas.git && cd uwas
make build # Production binary → bin/uwas
make linux # Cross-compile for Linux amd64
Quick Start
# Start server (auto-creates config on first run)
uwas
Or with a specific config
uwas serve -c uwas.yaml
Install as systemd service (auto-start on boot)
sudo uwas install
sudo systemctl start uwas
Dashboard
http://127.0.0.1:9443/_uwas/dashboard/
CLI commands (API key auto-loaded from ~/.uwas/.env)
uwas status
uwas php list
uwas domain list
uwas doctor
Configuration
UWAS uses a single YAML file. See uwas.example.yaml for a full reference.
Static Site
global:
http_listen: ":80"
https_listen: ":443"
acme:
email: [email protected]
domains:
- host: example.com
root: /var/www/html
type: static
ssl:
mode: auto
WordPress / PHP
domains:
- host: blog.example.com
root: /var/www/wordpress
type: php
ssl:
mode: auto
php:
fpm_address: "unix:/var/run/php/php8.3-fpm.sock"
htaccess:
mode: import
cache:
enabled: true
ttl: 1800
Standalone App + Domain Route
Apps live outside the main domain YAML. Create them from the dashboard
Applications page or as files under /etc/uwas/apps.d/; then route a
domain to the app by using apps:// as a proxy upstream.
# /etc/uwas/apps.d/my-api.yaml
name: my-api
runtime: node
work_dir: /var/lib/uwas/apps/my-api
port: 0
env:
NODE_ENV: production
uwas.yaml or domains.d/api.example.com.yaml
domains:
- host: api.example.com
type: proxy
ssl:
mode: auto
proxy:
upstreams:
- address: "apps://my-api"
Native runtimes with an empty workdir get a small demo file on create
(index.js, app.py, app.rb, or main.go) so the app has something
runnable immediately. Existing files are never overwritten.
Git Deploy + Private Repos + Webhooks
Applications can be deployed from a Git repo from the dashboard or by
persisting deploy settings in the app YAML. Private repositories can use
either a HTTPS token or an SSH deploy key. If build_cmd is empty, UWAS
auto-detects the build step from package.json, requirements.txt,
Gemfile, or go.mod; set build_cmd: none to skip builds. Set
health_path when deploys should require a 2xx/3xx HTTP response after
the process starts.
# /etc/uwas/apps.d/my-api.yaml
name: my-api
runtime: node
work_dir: /var/lib/uwas/apps/my-api
port: 0
env:
NODE_ENV: production
deploy:
git_url: https://github.com/acme/private-api.git
git_branch: main
health_path: /health
git_token: ghp_xxx
webhook_secret: a-long-random-secret
branch_filter: main
For SSH repositories:
deploy:
git_url: [email protected]:acme/private-api.git
git_branch: main
ssh_key_path: /home/uwas/.ssh/private-api-deploy-key
webhook_secret: a-long-random-secret
Add a GitHub webhook pointing at:
https://your-admin-host/api/v1/apps/my-api/webhook
Use application/json and the same webhook secret. GitLab can use the
same URL with X-Gitlab-Token. Accepted webhook pushes run clone/fetch,
auto build if needed, restart the app, verify that it is listening, and
check health_path when configured. If build, restart, or health
verification fails after an existing repo moved to a new commit, UWAS
resets the workdir to the previous commit and restarts the previous
version.
Reverse Proxy with WebSocket
domains:
- host: api.example.com
type: proxy
ssl:
mode: auto
proxy:
upstreams:
- address: "http://127.0.0.1:3000"
weight: 3
- address: "http://127.0.0.1:3001"
weight: 1
algorithm: least_conn
websocket: true
health_check:
path: /health
interval: 10s
Bandwidth Limits + Rate Limiting
domains:
- host: example.com
root: /var/www/html
type: static
bandwidth:
enabled: true
monthly_limit: 100GB
daily_limit: 5GB
action: throttle # or "block"
security:
rate_limit:
requests: 100
window: 1m
Requirements
| Component | Minimum | Recommended | Notes |
|-----------|---------|-------------|-------|
| Go | 1.26.10+ | 1.26.10+ | For building from source (1.26.10 carries the crypto/x509 GO-2026-6809 fix) |
| PHP | 8.2+ (8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or newer) | 8.4+ / 8.5+ | Only needed for PHP sites. 8.1 and older are end-of-life and unsupported; older patch releases are vulnerable to CVE-2026-109618/109619 |
| Docker | 20.10+ | 24+ | Only for Docker apps and database containers |
No PHP needed for static sites, reverse proxy, or redirect domains.
CLI
uwas Start server (auto-setup if no config)
uwas serve -c uwas.yaml Start with specific config
uwas serve -d Start as background daemon
uwas version Print version info
uwas config validate Validate config file
uwas domain list List domains
uwas cache stats Cache statistics
uwas cache purge Purge cache
uwas status Server status via admin API
uwas reload Hot-reload configuration
uwas stop Stop running server
uwas restart Restart running server
uwas migrate nginx <file> Convert Nginx config to UWAS
uwas migrate apache <file> Convert Apache config to UWAS
uwas backup Create config backup
uwas restore Restore from backup
uwas php list List detected PHP versions
uwas php start <ver> Start PHP-FPM for version
uwas install Install as systemd service
uwas uninstall Remove systemd service
uwas user list List admin users
uwas cert list List certificates + expiry
uwas doctor System diagnostics + auto-fix
uwas help Show help
Architecture
Request Flow:
TCP → TLS (SNI routing)
→ HTTP Parse
→ Global Middleware Chain:
Recovery → Request ID → Real IP → Security Headers → Compress
→ Rate Limit → Security Guard (blocked paths) → Bot Guard
→ Access Log
→ Virtual Host Lookup
→ Per-domain (inline in handleRequest, not middleware):
IP ACL → GeoIP → BasicAuth → CORS → Header Transform → WAF
→ Bandwidth Check (throttle/block)
→ Rewrite Engine (mod_rewrite compatible)
→ Cache Lookup (L1 memory + L2 disk)
→ Handler:
├── Static File (ETag, Range, pre-compressed, SPA)
├── FastCGI/PHP (connection pool, CGI env)
├── Reverse Proxy (6 LB algorithms, sticky, circuit breaker, gRPC)
├── WebSocket (TCP tunnel, bidirectional pipe)
└── Redirect (301/302/307/308)
→ Cache Store
→ Bandwidth Record
→ Response
Project Layout
cmd/uwas/ → CLI entry point (19 commands)
internal/
admin/ → REST API (253 route registrations) + dashboard embed + TOTP auth
apps/ authmw/ backup/ cloudflare/ database/ deploy/ domain/ files/
php/ settings/ wordpress/ → handler sub-packages (see docs/admin-subpackaging.md)
alerting/ → Alert thresholds + webhook/Slack/Telegram/email notifications
analytics/ → Per-domain traffic analytics
apps/ → Standalone Node/Python/Ruby/Go/custom/Docker app supervision
auth/ → Multi-user RBAC (admin/reseller/user) + session + TOTP 2FA
backup/ → Local/S3/SFTP backup + restore + scheduling
bandwidth/ → Per-domain bandwidth limits (throttle/block)
build/ → Build metadata (version, commit, date) via ldflags
cache/ → L1 memory (256-shard LRU) + L2 disk cache + ESI
cli/ → CLI framework and commands
cloudflare/ → Cloudflare Tunnel API + local cloudflared process
config/ → YAML parser, validation, defaults, ByteSize/Duration types
cronjob/ → Cron job management + execution monitoring
database/ → MySQL/MariaDB management + Docker container support
deploy/ → Git clone/pull + Docker-based application deployment
dnsmanager/ → Cloudflare, Route53, Hetzner, DigitalOcean DNS CRUD
dnschecker/ → DNS record verification (A/MX/NS/TXT)
doctor/ → System diagnostics + auto-fix
domainroot/ → Filesystem root resolution for a domain (apps:// aware)
domainutil/ → Pure hostname helpers shared by admin and admin/domain
filemanager/ → Web file manager (browse/edit/upload/delete)
firewall/ → UFW management via API
handler/
fastcgi/ → PHP handler, CGI environment builder
proxy/ → Reverse proxy, load balancing, WebSocket, circuit breaker
static/ → Static files, MIME, ETag, pre-compressed, SPA
install/ → System package installer task queue
logger/ → Structured logger (slog wrapper)
mcp/ → MCP server for AI management
metrics/ → Prometheus-compatible metrics
middleware/ → Chain, recovery, rate limit, gzip, CORS, WAF, bot guard
migrate/ → Nginx/Apache converter + SSH site migration + clone
monitor/ → Uptime monitoring per domain
notify/ → Webhook, Slack, Telegram, Email (SMTP) channels
pathsafe/ → Path traversal guard (symlink-resolving containment check)
phpmanager/ → PHP detect, install, start/stop, per-domain assign
respond/ → Centralized JSON response helpers (status + hardening headers + 5xx logging)
rewrite/ → URL rewrite engine (Apache mod_rewrite compatible)
rlimit/ → Per-domain resource limits via Linux cgroups v2
router/ → Virtual host routing, request context
selfupdate/ → Binary self-update from GitHub releases
server/ → HTTP/HTTPS/HTTP3 server + request dispatch + log rotation
serverip/ → Server IP detection (interfaces + public IP)
services/ → systemd service management (start/stop/restart)
sftpserver/ → Built-in SFTP server (pure Go, chroot per domain)
siteuser/ → SFTP user management (chroot jail + SSH keys)
terminal/ → WebSocket-to-PTY bridge for browser-based shell
tls/ → TLS manager, ACME client, auto-renewal, cert expiry alerts
acme/ → RFC 8555 ACME protocol, JWS signing
webhook/ → Event-driven webhook delivery (12 events + test, HMAC, retry)
wordpress/ → WordPress install, manage, debug, permissions
pkg/
fastcgi/ → FastCGI binary protocol, connection pool
htaccess/ → .htaccess parser and converter
web/dashboard/ → React 19 SPA (42 pages, Vite + TypeScript + Tailwind)
Dashboard
UWAS includes a 42-page React 19 dashboard at /_uwas/dashboard/ with dark/light theme:
Sites: Dashboard, Domains, Domain Detail, Topology, Certificates, DNS Zone Editor, Cloudflare, WordPress, Clone/Staging, Migration, File Manager
Server: PHP, PHP Config, Applications, Software Library, Database, DB Explorer, SFTP Users, Cron Jobs, Services, Packages, IP Management, Email Guide
Performance: Cache, Metrics, Analytics, Logs
Security: Security, Firewall, Unknown Domains, Audit Log, Admin Users
System: Setup Wizard, Config Editor, Webhooks, Backups, Terminal, Updates, Doctor, Settings, About
Not in the sidebar: Domain Detail, Login
Auth: Login (with 2FA/TOTP support)
Comparison
| Feature | UWAS | Nginx | Caddy | Apache | cPanel | |---------|------|-------|-------|--------|--------| | Single binary | Yes | No | Yes | No | No | | Auto HTTPS | Yes | No | Yes | No | Yes | | Built-in cache | Yes | No | No | No | No | | PHP FastCGI | Yes | Yes | Yes | Yes | Yes | | .htaccess support | Yes | No | No | Yes | Yes | | Load balancer | Yes | Yes | No | No | No | | WebSocket proxy | Yes | Yes | No | No | No | | WAF | Yes | No | No | Mod | Yes | | Control panel | Yes (built-in) | No | No | No | Yes | | Multi-user auth | Yes | No | No | No | Yes | | Webhook events | Yes | No | No | No | No | | DNS management | 4 providers | No | No | No | Yes | | MCP / AI-native | Yes | No | No | No | No | | Open source | AGPL-3.0 | BSD | Apache 2.0 | Apache 2.0 | Proprietary |
Performance
Tested with hey on AMD Ryzen 9 9950X3D:
| Scenario | Requests/sec | Avg Latency | |----------|-------------|-------------| | Small static file (14B) | 7,000 | 7.1ms | | 4KB static file | 7,100 | 7.0ms | | 100K requests @ 200 concurrent | 7,254 | 27ms | | 404 error page | 22,000 | 2.2ms | | Cache L1 lookup (bench) | 75,000,000 | 31ns | | VHost routing (bench) | 70,000,000 | 35ns |
Deployment
Systemd
sudo cp init/uwas.service /etc/systemd/system/
sudo systemctl enable uwas
sudo systemctl start uwas
Live config reload (zero downtime)
sudo systemctl reload uwas
Docker
The image runs as a non-root uwas user (with CAP_NET_BIND_SERVICE for ports
80/443), includes a healthcheck, and ships a baked default config that the
entrypoint seeds into a writable volume on first boot — so domain additions and
config edits made from the dashboard persist across restarts.
docker compose (recommended): includes MariaDB + PHP-FPM sidecars.
cp .env.example .env # set UWAS_ADMIN_KEY (required for the dashboard)
docker compose up -d
The admin API binds :9443 inside the container but Compose publishes it only
on host loopback (127.0.0.1:9443) because the default admin listener is HTTP.
For remote access, use ssh -L 9443:127.0.0.1:9443 user@server and open
http://127.0.0.1:9443/_uwas/dashboard/. Set UWAS_ADMIN_KEY in .env
(generate one with openssl rand -hex 24). The config lives in a named volume
(uwas_config); edit it from the dashboard or drop a custom
docker/uwas.yaml before the first boot.
docker run (standalone):
docker build -t uwas .
docker run -d -p 80:80 -p 443:443 -p 127.0.0.1:9443:9443 \
-e UWAS_ADMIN_KEY=your-admin-key \
-v uwas_config:/etc/uwas \
uwas
See docker-compose.yml and docker/uwas.yaml for the full reference.
Migration from Nginx/Apache
# Convert existing Nginx config
uwas migrate nginx /etc/nginx/sites-enabled/example.conf > uwas.yaml
Convert Apache config
uwas migrate apache /etc/apache2/sites-enabled/example.conf > uwas.yaml
Or use the dashboard Migration wizard for full site transfer (files + database)
Development
make dev # Build development binary
make dashboard # Build the React dashboard (required before go:embed)
make build # Production binary (includes dashboard build)
make test # Run all Go tests
make lint # Run go vet + staticcheck
make check # Pre-push gate: lint + tsc + tests
make clean # Clean build artifacts
Dashboard changes requirenpm installinweb/dashboard/, then
make dashboard to rebuild before the Go binary picks them up.
Monitoring (Prometheus + Grafana)
UWAS exposes a Prometheus-compatible metrics endpoint at /api/v1/metrics.
Scrape it with Prometheus:
scrape_configs:
- job_name: uwas
static_configs:
- targets: ["localhost:9443"]
bearer_token: "your-admin-key"
A ready-made Grafana dashboard is included at
docs/grafana/uwas-overview.json — import
it via Dashboards → Import → Upload JSON. It covers request rate, cache hit
ratio, latency (p50/p95/p99), handler distribution, bandwidth, and more.
License
UWAS is dual-licensed:
- AGPL-3.0 for open-source and community use — full text
- Commercial license for enterprise and proprietary use — uwaserver.com/enterprise
Contributing
- Open an issue first to discuss
- One feature/fix per PR
- Tests required
go vetmust pass