$npx -y skills add DeerYang/server-security-init-skill --skill server-security-initUse when configuring a newly installed or rebuilt Ubuntu/Debian server over SSH, especially when selecting or generating SSH keys, creating a non-root sudo user, changing SSH ports, disabling root/password login, enabling UFW or fail2ban, updating SSH config, or verifying that th
| 1 | # Server Security Init |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | Use this skill to harden a fresh SSH server without locking the user out. Treat SSH, firewall, and fail2ban changes as high-risk operations that require staged verification. |
| 6 | |
| 7 | This skill is intended for Ubuntu/Debian systems using OpenSSH, systemd, UFW, and fail2ban. For other distributions or firewalls, adapt carefully and state the differences before changing anything. |
| 8 | |
| 9 | ## Required Inputs |
| 10 | |
| 11 | Collect or infer these before making changes. If any value is ambiguous, ask before proceeding. For a fresh server request, ask for the bootstrap facts first instead of starting remote commands immediately. |
| 12 | |
| 13 | | Parameter | Meaning | |
| 14 | | --- | --- | |
| 15 | | `root_public_key_installed` | Whether the chosen SSH public key has already been installed for the initial server login user | |
| 16 | | `target_host` | IP address or existing SSH alias for the server | |
| 17 | | `initial_user` | Current login user, often `root` | |
| 18 | | `initial_port` | Current SSH port, often `22` | |
| 19 | | `admin_user` | Non-root sudo user to create | |
| 20 | | `public_key_source` | Existing remote authorized key, local `.pub` file, or pasted public key | |
| 21 | | `local_key_name` | Local key basename or server alias to use when selecting or generating a dedicated key | |
| 22 | | `identity_file` | Local private key path for future SSH config | |
| 23 | | `new_ssh_port` | Final SSH port | |
| 24 | | `ssh_config_alias` | Local SSH config alias to create/update | |
| 25 | | `management_ips` | Current and common admin egress IPs or jump hosts to whitelist in fail2ban | |
| 26 | |
| 27 | Optional settings: |
| 28 | |
| 29 | | Parameter | Default | |
| 30 | | --- | --- | |
| 31 | | `disable_root_login` | `true` | |
| 32 | | `disable_password_login` | `true` | |
| 33 | | `enable_ufw` | `true` | |
| 34 | | `allowed_tcp_ports` | only `new_ssh_port` | |
| 35 | | `enable_fail2ban` | ask or use user preference | |
| 36 | | `fail2ban_maxretry` | `3` | |
| 37 | | `fail2ban_findtime` | `86400` | |
| 38 | | `fail2ban_bantime` | `86400` | |
| 39 | | `generate_local_keypair` | ask during intake if no usable key exists | |
| 40 | | `local_key_comment` | `local_key_name` or user-specified comment | |
| 41 | | `local_key_passphrase` | ask; do not assume empty passphrase unless user prefers automation | |
| 42 | |
| 43 | ## Input Validation |
| 44 | |
| 45 | Validate values before substituting them into shell commands or config files: |
| 46 | |
| 47 | - `admin_user`: must be a normal Linux username such as `^[a-z_][a-z0-9_-]{0,31}$`; reject `root`, empty values, whitespace, shell metacharacters, path separators, and newline characters. If the account already exists, stop unless it is clearly an intended human admin account with UID >= 1000, a real home directory, and an interactive shell; never grant passwordless sudo to service/system accounts. |
| 48 | - `initial_port`, `new_ssh_port`, and `allowed_tcp_ports`: must be numeric TCP ports in `1..65535`; prefer unprivileged ports `1024..65535` unless the user explicitly chooses a privileged port. |
| 49 | - `public_key_source` or `public_key_content`: must resolve to exactly one valid non-empty SSH public key line. Verify with `ssh-keygen -lf` against a temporary public-key file and never print private key material. |
| 50 | - `target_host`, `ssh_config_alias`, and `identity_file`: must not contain newline characters. Treat paths and aliases as data, not shell fragments. |
| 51 | - `management_ips`: must contain only explicit IPs or CIDR ranges that the user recognizes. If the current egress IP cannot be determined confidently, ask before enabling fail2ban. |
| 52 | |
| 53 | ## Safety Rules |
| 54 | |
| 55 | - Do not disable root login, password login, the old SSH port, or the old firewall rule until `admin_user@new_ssh_port` has been verified in a separate SSH command. |
| 56 | - Keep an existing working SSH session open while changing SSH or firewall configuration. |
| 57 | - Back up files before editing: `/etc/ssh/sshd_config`, relevant `/etc/ssh/sshd_config.d/*.conf`, fail2ban jail files, and local `~/.ssh/config`. |
| 58 | - Prefer drop-in files for new SSH/fail2ban policy, but ensure earlier directives cannot override the intended final state. Verify with `sshd -T`. |
| 59 | - On systemd hosts, check `ssh.socket` before and after changing SSH ports. If socket activation is enabled, systemd may own the listening port and ignore `Port` values in `sshd_config`; prefer disabling `ssh.socket` and running the traditional `ssh.service` unless intentionally managing socket units. |
| 60 | - Use server-side truth for port/firewall verification: `ss -ltnp`, `sshd -T`, `systemctl status ssh.socket ssh.service`, and `ufw status`. Listener checks must include `systemd` as well as `sshd`; socket activation can make PID 1 own the old port. Local tests can be misleading under Clash/Mihomo TUN or fake-IP DNS. |
| 61 | - Configure fail2ban `ignoreip` before or while enabling fail2ban. Include the current admin egress IP and any usual jump/ |