SSH & Access Configuration
Architecture and access rules for secure shell and Git operations.
I run a customized, dual-port SSH setup, but instead of just opening up a general-purpose shell daemon to the world, I gave each port a very specific, narrowly-scoped job. I also route traffic through subdomains (like git.jelius.dev) rather than raw IP addresses. Why? Mostly so I don't have to memorize IPs, but it also means the underlying host IP can change without breaking everyone's Git config. Plus, it makes the access points naturally self-documenting.
Note that SSH is fundamentally different from the Layer 7 HTTP(S) traffic I outlined in CDN & Origin Shielding. CloudFront and Cloudflare can easily proxy HTTP, but SSH is a raw TCP protocol. Because of that, both SSH subdomains resolve directly to the host via plain A records—there is no CDN sitting in front of these ports.
Architecture Overview
Both ports are handled by the exact same underlying sshd service, separated simply by Port directives and per-port Match blocks in the config.
Port 22: Secure Monitoring
I left port 22 open on purpose. Closing it entirely just pushes automated scanner noise to logs elsewhere, since internet-wide bots are going to relentlessly hammer port 22 regardless of whether anything real is behind it. So, instead of hiding it, I gave them a toy to play with.
- Access Level: Zero interactive shell access. Period.
- Functionality: If you actually manage to authenticate with a key on port 22, you do not get a shell. I configured
sshdwithForceCommand btop(andPermitTTY yesso the TUI actually renders). The only thing that can ever run in that session is thebtopresource monitor. There is no bash shell underneath it to escape into—the SSH session's program isbtop. - Exit behavior: The moment
btopexits (viaqor a disconnect), the forced command's process ends, andsshdimmediately tears down the session. There is no shell left behind to fall through to, because one never existed in the first place.
The practical effect? I get a genuinely useful, at-a-glance system monitor from anywhere I have my key. Meanwhile, a scanner or opportunistic attacker gets absolutely nothing except read-only visibility into how much RAM they are currently wasting.
Port 69: Git & Administration
The real functional SSH daemon—the one I use for actual work—listens on a non-standard, unprivileged port: 69.
Moving the real daemon off port 22 mostly serves to cut down on log noise and automated brute-force attempts targeting standard SSH setups. It isn't a security control by itself (textbook "security through obscurity"), as the real enforcement is still key-only auth, strict user restrictions, and fail2ban watching the gates.
Access on this port is segregated entirely by which user authenticates:
1. Git Operations (git user)
- Endpoint:
git@git.jelius.dev - Access Level: Git wire-protocol commands only. No arbitrary command execution.
- Behavior: If you try to SSH in normally (
ssh git@git.jelius.dev), the server prints a success banner—Forgejo's polite way of saying "Hi! You successfully authenticated, but we don't provide shells"—and instantly kicks you out. It confirms your key works without actually giving you the keys to the kingdom.
2. Administrative Access
- Endpoint:
<ADMIN_USER>@<INTERNAL_SUBDOMAIN> - Access Level: Full interactive shell (bash/zsh). This is the only identity on this entire dual-port setup with this privilege.
- Mechanism: My admin user's
Match Userblock has noForceCommandat all, meaning I get a normal login shell exactly as you would on any ordinary server. All those paranoid restrictions above are specifically for thegitidentity and the port 22 honeypot. My admin account is deliberately the one exception. - Why a separate internal subdomain? Keeping the admin entrypoint on its own hostname (which isn't advertised anywhere near the
gitclone URL) ensures it doesn't show up in the one place meant to be public-facing. It keeps the actual maintenance door completely off the radar.