-
Notifications
You must be signed in to change notification settings - Fork 451
Deploying to a Server
RedAmon normally runs on your own machine at http://localhost:3000. This guide is for the other case: putting a full RedAmon instance on a Linux server on the internet - an AWS EC2 box, a DigitalOcean droplet, a Hetzner server, or bare metal - so you (and your teammates) can reach it from a browser over HTTPS.
You drive the whole thing from your laptop with one command. You never install anything on the server by hand.
Local vs. server - which do you want?
- Just trying RedAmon out, or working solo on your own laptop? Follow Getting Started instead. It runs on
localhostand needs none of this.- Want a shared, always-on instance reachable over the internet? You're in the right place.
Security first. RedAmon was designed to run local-only and trusts everything on its own network. Exposing it raw on a public IP would hand anonymous attackers an unauthenticated root shell (the Kali sandbox terminal), among other things. The deploy tool exists precisely to close that gap: it wraps RedAmon in nginx + TLS + a firewall + host hardening so that only the login page is reachable from the internet. Do not shortcut it by opening extra ports "to make things work" - see What the deploy protects you from.
If you already know your way around a cloud server, here's the whole flow:
# on your laptop, in the repo
cd deploy/single-host
cp .env.example .env
# edit .env: HOST_IP, DOMAIN, SSH_KEY_PATH, OPERATOR_ALLOW_CIDRS,
# LETSENCRYPT_EMAIL, ADMIN_NAME / ADMIN_EMAIL / ADMIN_PASSWORD
./deploy.sh initType INIT when prompted, wait 30-60 minutes for the first build, then log in at https://<your-domain>/. The rest of this page explains each step.
You need three things.
A fresh Ubuntu 22.04 or 24.04 box with a public IP. You install nothing on it manually - deploy.sh detects and installs every prerequisite for you (Docker, nginx, certbot, the firewall, swap, and so on), and it's safe to re-run.
RedAmon is heavy. Pick a size:
| Profile | vCPU | RAM | Disk | Example (AWS) | Notes |
|---|---|---|---|---|---|
| Bare minimum (core only) | 2 | 8 GB | 50-60 GB | t3.large | Works, but slow and swappy. |
| Recommended (core + occasional scans) | 4 | 16 GB | 60-80 GB | m5.xlarge | The sane default. |
| Full (GVM + Knowledge Base + local AI judge) | 8 | 32 GB | 100-120 GB | m5.2xlarge | Needed if you enable the heavy features. |
8 GB RAM is a hard floor. RedAmon refuses to start below it. If you must run on something smaller, you can override the check (see Troubleshooting), but expect it to be painful.
A DNS name like redamon.example.com with its A record pointing at your server's IP. This gets you a real, browser-trusted HTTPS certificate automatically (via Let's Encrypt), which is the default and by far the smoothest path.
No domain? You can still deploy on a bare IP, but you'll get a browser certificate warning (self-signed TLS). See Access modes.
Set up the DNS record before you deploy - Let's Encrypt validates the certificate by looking up your domain, so it has to resolve first.
bash, ssh, scp, and curl. Almost every Linux and macOS machine already has these. (Only if you insist on password-based SSH do you also need sshpass - but key-based SSH is recommended and needs nothing extra.)
You'll also need the SSH key (.pem file) that lets you log into the server, and the repository checked out on your laptop.
Before deploying, configure your cloud provider's firewall (AWS Security Group, GCP firewall, Azure NSG). This is your reliable outer wall. Open exactly three ports and nothing else:
| Port | Allow from | Why |
|---|---|---|
| 443/tcp | Your IP (or 0.0.0.0/0 if you trust the built-in gate) |
The RedAmon app (HTTPS) |
| 80/tcp | 0.0.0.0/0 |
Required so Let's Encrypt can issue your certificate, and to redirect visitors to HTTPS |
| 22/tcp | Your IP only | SSH management |
Do not open anything else. In particular, never open
8090,3000,5432,7687,7474, or4444. Those are internal services (including the anonymous root terminal) that stay locked to the server's own loopback interface. Your browser reaches everything it needs through port 443. Opening the others would defeat the entire security design.
On your laptop, from the repository root:
cd deploy/single-host
cp .env.example .envNow open .env in your editor. It's long, but for a standard HTTPS-on-a-domain deployment you only need to fill in a handful of fields. Here's a minimal, working example:
# --- how to reach the server ---
HOST_IP=203.0.113.50 # your server's public IP
REMOTE_USER=ubuntu # the SSH user (ubuntu on most cloud images)
SSH_KEY_PATH=~/.ssh/redamon.pem # your SSH key
# --- how the app is exposed ---
ACCESS_MODE=https-domain # the recommended default
DOMAIN=redamon.example.com # your domain (A record -> HOST_IP)
TLS_MODE=letsencrypt # free, browser-trusted certificate
LETSENCRYPT_EMAIL=you@example.com # for certificate renewal notices
# --- who can reach it ---
OPERATOR_ALLOW_CIDRS=203.0.113.10/32 # YOUR IP; only these can open the app
# --- the first login ---
ADMIN_NAME=Your Name
ADMIN_EMAIL=you@example.com
ADMIN_PASSWORD=<a-long-strong-password>That's enough for a secure, TLS-protected instance. A few notes on the important fields:
-
OPERATOR_ALLOW_CIDRSis your access list. Only these IP ranges can even see the login page. Set it to your office/home IP (e.g.203.0.113.10/32for a single address). You can list several, comma-separated. Leave it wide (0.0.0.0/0) only if you deliberately want the login page reachable from anywhere. -
ADMIN_PASSWORDmust be strong. There is no lockout after failed logins in the app itself (only a rate-limit at the edge), so a weak admin password is a real risk. Use a long, random one. -
SSH_KEY_PATHstays on your laptop and is never copied to the server.
Every other variable has a sensible default. When you need to tune something - extra features, custom TLS, tighter hardening, a non-standard SSH port - the Complete .env reference further down this page explains every single variable in detail.
These are off by default because they add build time and RAM pressure. Flip them to true in .env only if you have the capacity for them:
| Set | To enable | Cost |
|---|---|---|
ENABLE_GVM=true |
The GVM / OpenVAS vulnerability scanner | +10-20 min feed sync on first boot; needs the Full profile |
ENABLE_KB=true |
The local Knowledge Base (semantic search) | +~4.4 GB image, more RAM |
LLM API keys are not set here. You configure your AI provider keys inside the app after logging in - see AI Model Providers and Global Settings.
Still in deploy/single-host, run:
./deploy.sh initThe tool asks you to type INIT to confirm. This is a from-scratch build, and the wipe is total: it erases every Docker container, image, volume, and network on the server - not just RedAmon's. On a fresh, dedicated box that's exactly what you want; do not point init at a server that already runs other Docker workloads you care about. Once confirmed, it works through:
- Host bootstrap - installs Docker, nginx, certbot, the firewall, swap, and so on.
- Hardening - locks down SSH and the host firewall.
- Clone & build - pulls the code onto the server and builds all the images. This is the slow part: 30-60 minutes the first time, because of the large Kali and agent images. Grab a coffee.
-
First admin - creates the login account from your
.env. - nginx + TLS - issues your HTTPS certificate and stands up the reverse proxy.
-
Verification - confirms internal services are locked to loopback, containers are healthy, and
https://<domain>/api/healthreturns OK.
Preview without touching anything. Run
DRY_RUN=true ./deploy.sh initfirst to see exactly what the tool would do, without changing the server.
Want to watch the details? Add
VERBOSE=truein front of the command to stream the full remote output.
When init finishes, open https://<your-domain>/ in a browser (from an IP that's in your OPERATOR_ALLOW_CIDRS) and sign in with the admin email and password you set.
From here, the rest of the wiki takes over:
- User Management - add teammates
- Creating a Project - set up your first target
- Running Reconnaissance - run a scan
- AI Model Providers - plug in your LLM keys
ACCESS_MODE is the master switch for how the app is exposed. In almost all cases you want the first one.
| Mode | When to use it | TLS |
|---|---|---|
https-domain ✅ |
You have a domain. The default and recommended choice. | Free Let's Encrypt cert (TLS_MODE=letsencrypt) |
https-ip |
Bare IP, no domain, but you still want encryption | Self-signed (browser warning) or your own provided cert |
http-domain / http-ip
|
Lab/testing only - no encryption at all | None |
Avoid the
http-*modes on any real server. They send passwords and session cookies in plaintext and turn off the security headers. They're gated behindALLOW_INSECURE=1and a loud warning for exactly that reason. Only use them on a throwaway lab box you control end-to-end.
Using your own certificate? Set TLS_MODE=provided, drop fullchain.pem and privkey.pem into the cert/ folder, and the tool ships them to the server for you. This is the way to get a trusted certificate on a bare IP.
This is the full configuration surface, grouped exactly as it appears in .env.example. For a first deployment you only touch the handful in Step 2; everything else has a working default. Two things apply across the whole file:
-
CLI positionals override the connection fields.
./deploy.sh init 1.2.3.4 ~/.ssh/key.pem ubuntuoverridesHOST_IP,SSH_KEY_PATH, andREMOTE_USERfor that run. -
Multiple instances: keep one config per instance (
.env.prod,.env.staging,.env.<client>) and select it with--env NAME.
| Variable | Default | Detail |
|---|---|---|
HOST_IP |
(required) | Public IP or DNS name of the target server. This is where deploy.sh connects over SSH, and - in the *-ip access modes - the public address browsers use. It stays on your laptop and is never written to the server. Overridable as the first CLI positional. |
REMOTE_USER |
ubuntu |
The SSH login user. It must be able to run sudo (ideally passwordless). ubuntu is right for most AWS/cloud Ubuntu images; use admin, debian, or another sudoer as your image dictates. |
SSH_KEY_PATH |
~/.ssh/redamon.pem |
Path to your private SSH key. The recommended auth method. ~ is expanded. The key never leaves your laptop - it is not copied to the server. Overridable as the second CLI positional. |
SSH_PASSWORD |
(empty) | Password-auth fallback. Requires sshpass installed locally. Discouraged - and note that deploying over a password makes the hardening step leave password login enabled (so you can't lock yourself out), which is weaker. Prefer a key. |
SSH_PORT |
22 |
The port the server's sshd actually listens on. This value is shipped to the host: it drives both the ufw allow-rule for SSH and the fail2ban [sshd] jail. If your server moved SSH to a non-standard port, set this to match - otherwise the firewall could lock you out. |
| Variable | Default | Detail |
|---|---|---|
REPO_URL |
https://github.com/samugit83/redamon.git |
The public git URL cloned onto the server. No access token is needed. Point it at your own fork if you deploy custom code. |
REPO_BRANCH |
master |
The branch to deploy. ./deploy.sh update pulls this branch's latest commit. |
APP_DIR |
redamon |
The checkout directory name on the server (under the remote user's home). It also fixes COMPOSE_PROJECT_NAME, which RedAmon uses to recognize your existing database volumes and preserve their passwords. Never change it between init and update - doing so orphans your data volumes, and RedAmon would treat the instance as brand new. |
REDAMON_VERSION |
(empty) | An optional version string stamped into the built images for traceability. Blank uses RedAmon's own default. Cosmetic - it doesn't change behavior. |
| Variable | Default | Detail |
|---|---|---|
ACCESS_MODE |
https-domain |
The master switch: https-domain | https-ip | http-domain | http-ip. It derives the public host, the nginx listeners, the WebSocket scheme (wss/ws), and whether Secure cookies + HSTS are on. See Access modes. The two http-* values are lab-only and refuse to run without ALLOW_INSECURE=1. |
DOMAIN |
redamon.example.com |
Your DNS name. Required for the *-domain modes, ignored for *-ip. Its A record must resolve to HOST_IP before you deploy, because Let's Encrypt validates it. |
HTTP_PORT |
80 |
The port nginx listens on for HTTP and that ufw opens. Keep it 80 when using Let's Encrypt - the ACME http-01 challenge always validates on port 80. Change it only when another load balancer/proxy sits in front and presents 80/443 to clients itself. |
HTTPS_PORT |
443 |
The HTTPS port nginx listens on and ufw opens. Same caveat as HTTP_PORT: only change it behind a fronting load balancer. |
TLS_MODE |
letsencrypt |
How the certificate is obtained. letsencrypt (domain only; free, browser-trusted, auto-renewing) | provided (you supply the cert) | self-signed (IP only; generated on the host, browser warning expected). Consulted only for https-* modes. |
LETSENCRYPT_EMAIL |
ops@example.com |
Required for letsencrypt. Used for certbot registration and expiry/renewal notices. Use a mailbox you actually read. |
LETSENCRYPT_STAGING |
false |
Set true to issue against Let's Encrypt's staging CA while testing. Staging certs aren't browser-trusted, but they dodge the production limit of 5 certificates per week - invaluable while you iterate on DNS/firewall. Flip back to false for the real certificate. |
SSL_CERT_LOCAL |
cert/fullchain.pem |
provided mode only: local path (relative to deploy/single-host) to your full-chain certificate. It's SCP'd to /etc/ssl/redamon/ on the host, idempotently (by md5). Drop your files in the cert/ folder. |
SSL_KEY_LOCAL |
cert/privkey.pem |
provided mode only: local path to your private key. Copied to the host with 600 permissions. |
SSL_KEY_PASSWORD |
(empty) | The passphrase, if your provided private key is encrypted. |
HSTS_ENABLE |
true |
Emit the Strict-Transport-Security header on HTTPS, telling browsers to only ever use HTTPS for your domain. Automatically absent in the http-* modes. Leave it on for any real deployment. |
| Variable | Default | Detail |
|---|---|---|
OPERATOR_ALLOW_CIDRS |
203.0.113.10/32 |
A comma-separated CIDR list that does double duty: it's the nginx allow-gate (only these sources see the app) and the ufw source for port 443. Scope it to your own IP ranges. Using 0.0.0.0/0 means the login page is reachable from anywhere and you rely on the login + rate-limit alone. |
SSH_ALLOW_CIDRS |
(empty) | A comma-separated CIDR list for the ufw port-22 rule. If blank, it falls back to OPERATOR_ALLOW_CIDRS. Set it separately when you administer over SSH from a different location than you browse the app. |
GATE_MODE |
ip_allowlist |
How nginx gates the app: ip_allowlist (recommended - uses OPERATOR_ALLOW_CIDRS) | basic_auth (an HTTP Basic auth prompt in front of everything) | none (no edge gate; rely on the app login only). |
BASIC_AUTH_USER |
(empty) | Required when GATE_MODE=basic_auth: the username nginx demands before the app loads. |
BASIC_AUTH_PASS |
(empty) | Required when GATE_MODE=basic_auth: the matching password. |
WS_REQUIRE_SESSION |
true |
Makes nginx run an auth_request that validates a logged-in webapp session cookie before allowing any /ws/* WebSocket upgrade. This sits in front of the app-layer signed ws-ticket as a second layer. Keep it on - the agent WebSockets include a root PTY. |
CSP_ENFORCE |
false |
false runs the Content-Security-Policy in Report-Only mode (computed and reported, but nothing is blocked - the safe default). Set true to actually enforce the CSP, but only after confirming the WebGL attack-surface graph and the xterm terminal still render correctly. |
ENABLE_UFW |
true |
The host firewall (ufw). Leave on - it's a core part of the loopback-only posture. |
ENABLE_SSH_HARDENING |
true |
Enforce key-only login and disable root SSH. Safety valve: if you deployed over a password, it will not disable password login (so you can't be locked out). Install a key and re-run harden over it to fully close password auth. |
ENABLE_FAIL2BAN |
true |
Enable fail2ban jails for sshd and nginx that ban IPs after repeated failures. |
ENABLE_UNATTENDED_UPGRADES |
true |
Enable automatic, unattended installation of host security updates. |
| Variable | Default | Detail |
|---|---|---|
ENABLE_GVM |
false |
Maps to install --gvm. Turns on the GVM/OpenVAS vulnerability scanner. Heavy: expect a 10-20 min feed sync on first boot before scans return anything, and plan for the Full sizing profile. The deploy rotates GVM's default admin/admin credentials and prints the new password once - capture it. |
ENABLE_KB |
false |
Maps to install --kbase. The real Knowledge Base switch: it bakes ML embedding models into the image (+~4.4 GB) and enables semantic search over your KB. |
ENABLE_KB_REFRESH |
false |
Enables the kb-refresh sidecar that periodically refreshes Knowledge Base content. |
ENABLE_ZRAM |
true |
Maps to REDAMON_ENABLE_ZRAM=1. Enables a compressed-RAM (zram) cushion that helps memory-tight hosts survive spikes. |
SKIP_KBis deliberately not a variable you set. RedAmon derives it from the.kbase-enabledflag it manages internally. Control the Knowledge Base withENABLE_KBonly.
| Variable | Default | Detail |
|---|---|---|
SWAP_SIZE_GB |
8 |
Size (GB) of the swapfile the deploy creates when host RAM is under 16 GB. 0 skips swap creation. On small boxes keep it on - RedAmon's 8 GB RAM floor is tight and swap prevents hard OOM kills. |
REDAMON_SKIP_RAM_GATE |
false |
true bypasses RedAmon's hard 8 GB RAM floor so it will boot on an undersized box. Risky; if you use it, keep SWAP_SIZE_GB on and expect heavy swapping. |
REDAMON_BUILD_PARALLEL |
(empty) | Caps how many container images build concurrently. Blank lets RedAmon auto-size from the host's CPU/RAM. Lower it (e.g. 1 or 2) if the build itself runs out of memory. |
DOCKER_DNS |
(empty) | Comma-separated resolvers (e.g. 8.8.8.8,8.8.4.4) merged into /etc/docker/daemon.json. Set this only if container DNS resolution is broken on your host; otherwise leave it blank. |
DOCKER_BUILD_CACHE_MAX_GB |
(empty) | Ceiling (GB) for the Docker BuildKit build cache, applied as builder.gc.defaultKeepStorage in /etc/docker/daemon.json so the daemon auto-garbage-collects the cache to that size. update reuses the cache for fast incremental rebuilds but never prunes it, so over many updates it grows unbounded and slowly eats disk. Set e.g. 30 to bound it without slowing rebuilds (the cap only evicts the least-useful layers). Blank leaves Docker's default GC. Changing it triggers a one-time docker restart during bootstrap. |
| Variable | Default | Detail |
|---|---|---|
REVSHELL_TARGET_CIDRS |
(empty) | The Rules-of-Engagement target scope for ./deploy.sh revshell-open. It scopes the temporary ufw rule for port 4444 to just these CIDRs. Empty means port 4444 stays fully closed. See Catching reverse shells. |
TUNNELS_ENABLED |
false |
Keep off on an internet-exposed host. It controls the outbound tunnel managers (ngrok/chisel-style); you don't want those available on a public box unless you're deliberately using one, and never alongside a world-open 4444. |
| Variable | Default | Detail |
|---|---|---|
ADMIN_NAME |
(required) | Display name of the first admin user, created automatically during init. |
ADMIN_EMAIL |
(required) | Login email of the first admin. |
ADMIN_PASSWORD |
(required) | The first admin's password. There is no app-layer login lockout - nginx's limit_req rate limit is the only brute-force brake - so this must be long and random. |
| Variable | Default | Detail |
|---|---|---|
NVD_API_KEY |
(empty) | An NVD API key. Grants a higher rate limit for CVE/NVD lookups, so vulnerability enrichment runs faster. |
KB_EMBEDDING_USE_API |
(empty) | Set to use a remote embedding API instead of the baked-in local model. Lets you skip the +4.4 GB model image and offload embeddings to a service. |
KB_EMBEDDING_API_BASE_URL |
(empty) | Base URL of that remote embedding API. |
KB_EMBEDDING_API_KEY |
(empty) | Auth key for the remote embedding API. |
LLM provider keys are not set here. Configure your AI provider credentials inside the app after logging in - see AI Model Providers and Global Settings.
| Variable | Default | Detail |
|---|---|---|
INIT_FORCE |
false |
true skips the typed INIT wipe confirmation. CI only, dangerous - it removes the last guard before a total Docker wipe. Never set it interactively. |
BACKUP_BEFORE_UPDATE |
false |
Reserved for a pre-update database snapshot (Postgres dump + Neo4j snapshot). Currently a placeholder flag. |
DRY_RUN |
false |
true prints the fully resolved plan and exits without touching the host. Run DRY_RUN=true ./deploy.sh init to preview exactly what will happen. |
VERBOSE |
false |
true streams the full remote command output instead of the summarized progress view. Useful when a step fails and you need to see why. |
ALLOW_INSECURE |
0 |
Must be 1 to permit the plaintext http-* access modes. A guardrail: those modes invert the whole security posture (plaintext cookies/credentials, no HSTS), so they refuse to run without this explicit opt-in. |
All of these run from your laptop, from the same deploy/single-host directory. None of them (except init) touch your data - all your projects, scans, reports, and graph data live in Docker volumes that survive updates.
| Command | What it does |
|---|---|
./deploy.sh update |
Pull the latest code and rebuild only what changed. Keeps all your data. |
./deploy.sh status |
Health check: containers, firewall, nginx config, certificate expiry. |
./deploy.sh logs [service] |
Tail live logs (defaults to the agent service, e.g. ./deploy.sh logs webapp). |
./deploy.sh ssl-renew |
Renew or re-install the TLS certificate and reload nginx. |
./deploy.sh harden |
Re-apply host hardening + nginx/TLS only, without rebuilding. |
./deploy.sh down |
Stop the stack (keeps all data and images). |
Managing more than one instance? Keep separate config files like
.env.prodand.env.staging, and select one with--env, e.g../deploy.sh status --env staging.
One rule that matters: never SSH into the server and run
docker compose upby hand. That reloads the wrong configuration and would republish the internal ports on the public interface. Always go through./deploy.sh. (The same applies toredamon.sh up devon the host - it hardcodes its own compose files and defeats the production overlay.)
./deploy.sh update is the routine you'll use most. It pulls the latest commit of your REPO_BRANCH, then rebuilds only the images that actually changed. It is non-destructive: all your engagement data - Postgres, Neo4j, reports, GVM feeds, embedding models - lives in named Docker volumes that survive the update untouched. Database passwords are preserved too, because RedAmon detects the existing volumes by the stable COMPOSE_PROJECT_NAME (which is why you must never change APP_DIR between runs).
What update does under the hood: the deploy applies three small code changes to the checkout on the server at deploy time (they aren't committed to the app source): the baked single-origin WebSocket URL, a WebSocket-origin fix for CypherFix, and the Secure-cookie flip for HTTPS. Before pulling, update restores those files so the tree fast-forwards cleanly, runs the pull + selective rebuild, then re-applies the patches and rebuilds the webapp image so the single-origin URL and Secure-cookie behavior survive. Finally it re-renders the nginx config.
Rollback is manual. There is no one-command rollback. To go back to a previous version, SSH to the server, git reset --hard <previous-commit> inside the ~/<APP_DIR> checkout, then run ./deploy.sh update from your laptop to rebuild against that commit. Because your data is in volumes, a code rollback doesn't lose engagement data - but a schema change that already migrated your database may not cleanly roll back, so treat major-version downgrades with care.
Back up before big changes.
BACKUP_BEFORE_UPDATEis currently a reserved placeholder, so take your own snapshot before a risky update: apg_dumpof Postgres and a Neo4j dump from inside their containers, or a provider-level disk snapshot of the whole server.
You don't need this to deploy, but it demystifies what the one command is doing.
deploy.sh is a thin remote driver around RedAmon's own redamon.sh. It doesn't reimplement RedAmon - it prepares a bare host, gets the repo onto it, and drives redamon.sh install/update over SSH, then wraps the result in the internet-facing security layer (nginx + TLS + firewall + hardening) that redamon.sh deliberately omits because RedAmon is designed to run local-only. It never edits redamon.sh; it steers it through four channels: the subcommand, CLI flags on install, exported environment variables, and keys appended to the server-side application .env.
The host is bootstrapped dynamically. On init (and harden), the deploy runs a detect-and-install matrix - each item is probed and installed only if missing or too old, idempotently: base packages (git, openssl, curl, jq, expect…), Docker Engine + Compose v2 from Docker's official repo (purging any old docker.io/v1 compose first), the docker service and group membership, nginx, certbot (only when Let's Encrypt is used), ufw, fail2ban, unattended-upgrades, an 8 GB swapfile on hosts with < 16 GB RAM, optional Docker DNS, and raised inotify limits. Nothing is assumed to be pre-installed, which is why you can point it at a stock Ubuntu image.
nginx runs on the host, not in a container, and only at the edge. It listens on 80/443 and reverse-proxies to two loopback backends: the webapp (127.0.0.1:3000, serving / and /api/*) and the agent (127.0.0.1:8090, serving only the four /ws/* WebSocket paths - never any agent REST route). It is not a middlebox between containers: all internal service-to-service traffic (webapp ↔ agent ↔ orchestrator ↔ databases ↔ Kali) flows directly over the Docker bridge networks and never passes through nginx. That's the whole reason only port 443 needs to be public - the browser reaches the agent's WebSockets at wss://<domain>/ws/… on 443, and nginx forwards them to 127.0.0.1:8090 internally.
The prod overlay makes the loopback binds stick. The deploy ships a production compose overlay and points redamon.sh at it via a sticky COMPOSE_FILE. That overlay is what rebinds 3000/8090/4444 to 127.0.0.1. It holds only while that overlay is active - which is exactly why a bare docker compose up (or redamon.sh up dev) on the host is forbidden: it would reload the base file and republish those ports on 0.0.0.0.
Port 4444 (the reverse-shell catcher) is closed by default. To catch a direct reverse shell during an authorized engagement, scope it to your target's IP range and open it only for as long as you need:
# in .env, set your Rules-of-Engagement target scope:
REVSHELL_TARGET_CIDRS=198.51.100.0/24
./deploy.sh revshell-open # opens 4444 to those targets only
# ... run your engagement ...
./deploy.sh revshell-close # close it the moment you're doneSet your payload's LHOST to the server's public IP and start the handler inside RedAmon. The opening is deliberately fragile - a server reboot closes it automatically - so it can never be left open by accident. See Reverse Shells for how to work with the sessions themselves.
You don't need to understand the internals, but it helps to know why the "open only three ports" rule is non-negotiable. In plain terms, the deploy gives you:
- One public door. Only the webapp login (over HTTPS) is reachable from the internet. The agent API, the databases, the Kali sandbox, and the anonymous root terminal all stay bound to the server's loopback interface, unreachable from outside.
-
A login gate before the app even loads. Your
OPERATOR_ALLOW_CIDRSallowlist (or optional basic-auth) sits in front of everything. - A logged-in-session requirement on the agent's live connections, on top of a signed-ticket check in the app itself.
- A hardened edge - modern TLS only, strict security headers, brute-force rate-limiting on the login endpoint, and stripping of any headers a client might use to fake its identity.
- A hardened host - firewall, fail2ban, automatic security updates, and key-only SSH.
- A secrets check that refuses to build if any password or key is left at a default or weak value.
The one thing the tool can't do for you is your cloud provider's firewall. Docker can sometimes bypass the host firewall, so your Security Group / NSG is the reliable outer boundary. That's why Step 1 matters: open only 443, 80, and 22.
The build aborts saying it needs ~8 GB of RAM.
Your server is too small. Move to a bigger instance, or (at your own risk) set REDAMON_SKIP_RAM_GATE=true and keep swap on with SWAP_SIZE_GB=8. Expect heavy swapping.
Certificate issuance (certbot) fails.
Two things must be true: your DOMAIN must resolve to the server's IP, and port 80 must be open to the world in your cloud firewall (Let's Encrypt validates from unpredictable IPs). While you're sorting it out, set LETSENCRYPT_STAGING=true to test against Let's Encrypt's staging server without burning through the weekly rate limit - then flip it back to false for the real certificate.
GVM scans return nothing right after deploy.
If you enabled ENABLE_GVM, its vulnerability feeds take 10-20 minutes to sync on first boot. Scans won't produce results until that finishes.
I locked myself out over SSH.
If you deployed using a password (rather than a key), the hardening step deliberately leaves password login enabled so you can't be locked out. Install an SSH key, then re-run ./deploy.sh harden over the key connection to fully close password login.
A deploy step failed halfway.
First look at what happened: ./deploy.sh status shows the current state, and ./deploy.sh logs <service> tails a specific container. The non-destructive commands (status, harden, ssl-renew, update) are idempotent and safe to re-run - they detect what's already done. Note that re-running init does not resume: it wipes everything and rebuilds from zero (another 30-60 min). If the build failed and your config was fine, ./deploy.sh update is usually the faster way to retry, since it rebuilds without the full wipe.
For a complete, variable-by-variable reference (every .env key, the exact security posture, the update/rollback mechanics, and the host bootstrap matrix), see the single-host deploy README in the repository.
With RedAmon live on your server:
- User Management - invite your team
- Creating a Project - configure your first target
- Rules of Engagement - set the scope before you scan
- Global Settings - API keys and agent configuration
Getting Started
- Getting Started
- Deploying to a Server
- User Management & Roles
- Creating a Project
- Recon Presets
- Global Settings
Core Workflow
- Red Zone
- Recon Pipeline Workflow
- Running Reconnaissance
- AI Agent Guide
- Fireteam — Parallel Specialists
- Agent Workspace
- Reverse Shells
Scanning & OSINT
- Adversarial AI Recon
- AI Gauntlet
- JS Reconnaissance
- GraphQL Security Testing
- Subdomain Takeover Detection
- VHost & SNI Enumeration
- Web Cache Poisoning
- GVM Vulnerability Scanning
- GitHub Secret Hunting
- TruffleHog Secret Scanning
AI & Automation
- AI Model Providers
- MCP Tool Plugins
- Knowledge Base & Web Search
- Agent Skills
- Chat Skills
- Tradecraft Lookup
- Playwright Browser Automation
- CypherFix — Automated Remediation
- Rules of Engagement (RoE)
HackLab
Analysis & Reporting
- Insights Dashboard
- Pentest Reports
- Attack Surface Graph
- Surface Shaper
- EvoGraph — Attack Chain Evolution
- Data Export & Import
Contributing
Reference & Help