SSem SecuMon free guide
Starting with a single personal server · as of October 2026
Coming soonInstaller downloads are still being prepared.
This guide publishes the prerequisites, the installation steps and what to know while running it ahead of time. Once downloads begin, the address will be posted here.
1. Prerequisites
| Item | Requirement |
|---|---|
| Servers to protect | Linux (systemd) + fail2ban · nftables · sqlite3. amd64 or arm64. Tested on Ubuntu 22.04 · CentOS 8. |
| Console server | One. JDK 21 · Tomcat 11 · PostgreSQL 17. Easiest to run with Docker. |
| Network | Your servers must be able to reach the console address over HTTPS. No inbound ports to the servers are needed. We recommend keeping the console behind a VPN or an internal network. |
| Clock | Sync the clocks of every server and the console with NTP. Requests whose signature time is more than 60 seconds off are rejected. |
| Certificate | An HTTPS certificate for the console (e.g. Let's Encrypt). The login cookie is sent only over HTTPS, so you cannot log in over plain HTTP. |
First install the packages on every server you want to protect.
# Debian · Ubuntu apt-get update && apt-get install -y fail2ban nftables sqlite3 # RHEL · Rocky · CentOS dnf install -y epel-release && dnf install -y fail2ban nftables sqlite systemctl enable --now fail2ban
2. Turn on fail2ban exponential bans
Configure fail2ban to block the same IP for longer each time it comes back. SecuMon uses these multipliers to work out “which level it is at now” and “how long the next ban will be”.
# /etc/fail2ban/jail.local [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 bantime.increment = true bantime.multipliers = 1 2 4 8 16 32 64 168 bantime.maxtime = 168h ignoreip = 127.0.0.1/8 ::1 <your VPN range> # /etc/fail2ban/fail2ban.local — keep ban history for 60 days (with the 1-day default, a repeat offender counts as new the next day) [DEFINITION] dbpurgeage = 60d
With the settings above, ban lengths are as follows. fail2ban adds a little random time, so actual values vary slightly.
| Cumulative bans | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th+ |
|---|---|---|---|---|---|---|---|---|
| Ban length | 1 hour | 2 hours | 4 hours | 8 hours | 16 hours | 32 hours | 64 hours | 7 days |
Be sure to add your own access paths (your VPN range, etc.) to ignoreip. This keeps you from being banned from your own server after a few mistyped passwords.
3. Install the console
3-1. Secret files
Secrets are kept as files in one folder, never in code or environment variables. The console container mounts this folder read-only at /App/secrets.
mkdir -p /srv/secumon/secrets/tls && chmod 700 /srv/secumon cd /srv/secumon/secrets openssl rand -hex 24 > db_password openssl rand -base64 18 > admin_password # first login password (account name: admin) openssl genpkey -algorithm ed25519 -out console_ed25519.key openssl pkey -in console_ed25519.key -pubout -out console_ed25519.key.pub # files are 644 so the container account can read them — the parent folder’s 700 does the protecting chmod 644 db_password admin_password console_ed25519.key console_ed25519.key.pub
| File | Purpose | If missing |
|---|---|---|
db_password | Database access | The console won’t start |
admin_password | Password for the first administrator account, admin | No account is created, so nobody can log in |
console_ed25519.key · .pub | Command signing | View only — no enforcement |
abuseipdb.key | AbuseIPDB API key (optional) | No reports · reputation lookups |
telegram.env | Telegram notifications (optional) | No notifications are sent |
tls/cert.pem · chain.pem · privkey.pem | Console HTTPS certificate | The console won’t open over HTTPS |
console_ed25519.key alone can sign valid commands for every server. Don’t keep this file in a repository or a shared drive — store it separately.
3-2. Database
The DB port is never exposed to the outside. The console connects by container name within the same Docker network.
docker network create secumon-net docker run -d --name secumon-db --network secumon-net --restart unless-stopped \ -e POSTGRES_DB=secumon -e POSTGRES_USER=secumon \ -e POSTGRES_PASSWORD_FILE=/run/secrets/db_password \ -v /srv/secumon/secrets/db_password:/run/secrets/db_password:ro \ -v secumon_pgdata:/var/lib/postgresql/data \ postgres:17
The console creates its tables and default data by itself on first start. You don’t need to load a schema separately with psql.
3-3. Console settings
The console is a web app that runs on Tomcat 11. How to run it will be published with the release package; here are the environment variables to decide on first.
| Environment variable | Default | Meaning |
|---|---|---|
F2B_DB_URL | jdbc:postgresql://…:5432/secumon | DB address. Use the container name. |
F2B_CANONICAL_HOST | Required: set your own | Your console domain. Requests over plain HTTP or to any other address are redirected to HTTPS on this address. |
F2B_PROTECTED_CIDRS | Required: set your own | Ranges never to block — your VPN · internal network · servers’ public IPs. Private networks · loopback are always protected regardless of this value. |
F2B_BLOCK_TTL_DAYS | 30 | Default range block duration (days). 0 means permanent. |
F2B_AUTO_BLOCK_ENABLED | true | Turns automatic blocking of repeat offenders on or off. |
F2B_AUTO_BLOCK_THRESHOLD | 3 | Ban count at which automatic blocking starts. |
F2B_AUTO_BLOCK_TTL_DAYS | 30 | Automatic block duration (days). |
F2B_FEED_REFRESH_HOURS | 24 | How often public blocklists · country lists are re-fetched (hours). |
F2B_BAN_MULTIPLIERS | 1 2 4 8 16 32 64 168 | Must match the multipliers in your jail settings exactly. |
F2B_ABUSE_DAILY_LIMIT | 1000 | AbuseIPDB daily usage limit. |
After it starts, check its status. If you see "db":"ok", it is working.
curl -s https://<console address>/api/health
4. Register servers — the agent
- Log in to the console as
admin, enter the server name on the Targets tab and click Register + issue token. - The target ID and a one-time token appear on screen. The token is shown only this once and expires after 15 minutes.
- On the server you want to protect, run the install script as root.
# on the server to protect (root) tar xzf agent-amd64.tgz && cd agent ./install-agent.sh --console https://<console address> \ --target-id <target ID> --token <one-time token> journalctl -u secumon-agent -f # a line like the one below within 30 seconds means it works
When it works, you will see a line like this secumon-agent: 2026/10/06 16:40:12 전송 완료 — jail 2개, 밴 5건
This is everything the install script does. It opens no firewall ports.
- Creates a dedicated account,
f2bagent, that cannot log in — the agent never runs as root. - Allows only one root wrapper,
/usr/local/sbin/f2b-agent-exec, through sudo. - Generates the agent’s signing key on that server. The private key never leaves over the network.
- Registers with the console, then keeps the agent running as the
secumon-agentservice.
Don’t use a host name containing an underscore (_) in the console address. Tomcat 11 rejects it as a spec violation. First check that the server can resolve the console domain with getent hosts <console domain>.
If registration succeeded but a later step failed, fix the cause and run the exact same command again. An agent that is already registered doesn’t use the token again.
To remove a server, Deactivate it in the console (requests from that agent are rejected immediately), then run systemctl disable --now secumon-agent on the server.
5. Reading the screens
- Dashboard — current bans · cumulative bans · repeat offenders · trends for every server. If a server card’s “Last received” is well over 30 seconds, check that server’s agent.
- Ban list — IPs blocked right now, shown with repeat-offense level · time left · country. Countries are looked up in a table built into the console.
- IP details — the servers that banned the IP, its ban history, the expected length of its next ban, and its AbuseIPDB reputation.
“Current bans” are read from the list fail2ban is actually blocking right now, and “repeat count” separately from fail2ban’s ban records. That way, bans that have already expired never show up as current bans.
6. Ban · unban · range block
- Ban · unban is available to operators and above. Any IP can be banned; if you don’t pick a jail,
sshdis used. - Range block is for administrators only. You must re-type the range before the block runs, and ranges wider than IPv4 /16 · IPv6 /48 are rejected. By default it lifts itself after 30 days.
- You can block several ranges × several servers at once. Nothing is issued unless every entry passes the checks — a half-applied block is the most dangerous state.
- A command runs only if the server’s agent picks it up within 60 seconds. Check the outcome — completed · failed · expired — in the command history on the Enforcement tab.
If you blocked something by mistake, lift it in the console with Enforcement → Unblock. If it’s too urgent to wait until you can get into the console, delete it directly on that server.
# just one range nft delete element inet f2b_console blocklist4 '{ 203.0.113.0/24 }' # every block SecuMon added (fail2ban rules live in a different table and stay intact) nft delete table inet f2b_console
7. Blocking automatically
Automatic blocking of repeat offenders
Every 5 minutes, IPs banned 3 or more times are picked out and blocked for 30 days on every server. This is the only feature that changes the firewall without human approval, so it is wrapped in several layers of safeguards.
- Blocks single IPs (
/32·/128) only, never ranges. - Protected ranges are skipped. At most 20 blocks are issued per run.
- The same IP is never escalated twice, and everything is recorded in the audit log and on Telegram.
To turn it off, set F2B_AUTO_BLOCK_ENABLED=false on the console and restart it. For an IP blocked by mistake, first lift it with Unblock, then delete its escalation record so it will be evaluated again later.
docker exec secumon-db psql -U secumon -d secumon -c "DELETE FROM auto_block WHERE ip = '<IP>'"
Public blocklists
Turn them on in the Blocklist feeds tab. Both are off at first.
- Spamhaus DROP — ranges that are hijacked or used only for crime. No legitimate traffic can come from them, so false positives are hardly a concern.
- FireHOL level1 — unused addresses and confirmed malicious ranges. It also includes private networks, so the console filters out entries that overlap protected ranges before sending it.
The lists are re-fetched once a day, and nothing is re-sent to servers if they haven’t changed. Ranges dropped from a list are lifted automatically at the next update.
8. Country blocking — before you turn it on
Services that must be reachable from all over the world can’t be blocked by country.
- Authoritative DNS servers — resolvers around the world must reach you for your domain to resolve. SecuMon always allows port 53.
- Inbound mail (25) · VPN — blocking them cuts off your mail, and you can’t get into your own server from abroad. Don’t turn on country blocking on such servers.
- Search engine bots — blocking a large country blocks search bots as well, and your website may disappear from search results.
Choose a country on the Country blocking tab and it first shows how many ranges that country has. When you apply it, the list is fetched on the spot and reaches every server within 30 seconds; turning it off deletes that country’s list. A large country can have tens of thousands of ranges.
Even a country that sends many attacks is often really just a few ranges · rented servers in that country. Block it and the attackers move to servers in another country. Think of country blocking as a supplement to use alongside public blocklists · automatic blocking of repeat offenders.
SecuMon allows replies on established connections at the very top of its firewall rules. Without this rule, even the replies to your server’s own outgoing connections to services in that country (package mirrors · notification APIs · certificate issuance) would be blocked.
9. AbuseIPDB · Telegram
AbuseIPDB
Put abuseipdb.key in the secrets folder to turn it on. The key stays only on the console server and never goes out to the screen · DB · servers.
- You can report only IPs that are banned now or were banned within the last 30 days — never arbitrary IPs.
- The same IP is not reported again within 15 minutes. The console keeps count of the daily quota itself.
- Categories are chosen by jail name — ssh → 18 · 22, web (apache · nginx) → 18 · 21, mail → 18 · 11, anything else → 18.
Telegram
# /srv/secumon/secrets/telegram.env
TELEGRAM_TOKEN=<bot token>
TELEGRAM_CHAT_ID=<chat ID>
Notifies you of automatic repeat-offender blocks and public list changes. The full list is attached as a file. Nothing is sent when nothing has changed — a daily “no changes” message would only make you miss the alerts that matter.
10. Accounts · two-factor authentication
| Role | What it can do |
|---|---|
VIEWER viewer | View the dashboard · ban list · IP details · command history |
OPERATOR operator | + ban · unban · AbuseIPDB reports and reputation lookups |
ADMIN administrator | + range block · server registration · blocklist feeds · country blocking · users · audit log |
- For the first login, use
adminand the password in theadmin_passwordfile. After logging in, change the password in My account. - In My account → Two-factor authentication, scan the QR code with an authenticator app or enter the code manually. The same code can’t be used twice.
- Five wrong passwords lock the account for 15 minutes, and a single IP can try at most 10 times per minute. You are logged out after 30 minutes of inactivity.
If you lose your authenticator app, it can’t be reset from the screen. Someone with access to the console server resets it directly in the DB.
docker exec secumon-db psql -U secumon -d secumon -c \ "UPDATE app_user SET totp_enabled=false, totp_secret=NULL, totp_last_counter=0 WHERE username='admin';"
11. Backups · key management
# database — passwords are hashed, and registration tokens are stored only as hashes docker exec secumon-db pg_dump -U secumon secumon | gzip > secumon-$(date +%F).sql.gz # secrets folder — this is the real secret. Keep it somewhere other than the DB backup tar czf secumon-secrets-$(date +%F).tgz -C /srv/secumon secrets
- If you change the console signing key, every agent rejects commands. If you did change it, re-register each server in this order: deactivate → reissue token → delete
/etc/secumon/agent.json·agent_ed25519.keyon the server → reinstall. - Check regularly: every day, server status and failed commands; every week, rejections in the audit log and accounts without two-factor authentication.
12. Troubleshooting
Agent and console messages are printed in Korean. Search the log for the exact text shown in code style in the table below.
| Symptom | Cause | What to do |
|---|---|---|
Agent log 등록되지 않았거나 비활성 상태인 타겟 | The server was deactivated in the console | Get a new token and re-register |
Agent log 요청 시각 편차가 허용치를 넘습니다 | The server clock is more than 60 seconds off | Sync the clock with NTP |
Agent log 이미 사용된 nonce | The same request arrived again | This is normal protection. If it keeps happening, suspect duplicate transmission on the network |
Command result 거부: 만료된 명령 | The agent didn’t pick it up within 60 seconds | Check the status of secumon-agent on the server and run it again |
Command result 거부: 서명이 유효하지 않음 | The console signing key was changed without re-registering | Re-register following the steps in section 11 |
| The server card’s last received time stops · the log goes silent | The agent has stalled | systemctl restart secumon-agent |
| Country blocking is on, but nothing changes on the server | This line is missing from the agent log: 국가 차단 적용 | Check that the agent is the latest version and look at the errors in its log |
| You keep getting logged out | Accessing the console over a non-HTTPS address | Use the console’s HTTPS address |
For anything else, write to halo@levelupsoft.com.
fail2ban · nftables · AbuseIPDB · Spamhaus · FireHOL · Telegram · Let's Encrypt belong to their respective owners. SSem SecuMon is not affiliated with or endorsed by them.