Skip to content

Remember a non-standard SSH port per server [15m] - #15

Open
jpawlowski wants to merge 5 commits into
wintermeyer:mainfrom
jpawlowski:feat/remember-ssh-port
Open

jpawlowski wants to merge 5 commits into
wintermeyer:mainfrom
jpawlowski:feat/remember-ssh-port

Conversation

@jpawlowski

@jpawlowski jpawlowski commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Gap: heinzel stored the SSH user per host but no port. A host on another port worked only if ~/.ssh/config carried it, and the firewall rules (ufw allow OpenSSH, firewalld's ssh service) open 22 only.

Change: New rules/ssh-port.md. A port other than 22 goes into server memory as - SSH port:, and every call adds -o Port=. On first contact, when 22 refuses, heinzel checks known_hosts, tries the user's own Alternative SSH ports: from memory/user.md, then asks. No built-in port list, no scanning. Alias detection also compares the port; firewall changes keep every port sshd listens on open.

Size: Rules only. No merge order; shared files follow #27, and I take in main after each merge.

Verified: guard test matrix 549 passed, 0 failed. ssh-keygen -F '[host]:port' checked against a hashed known_hosts file (OpenSSH 10.3).

Part of #27.

Closes #32.

An AI agent wrote this text in my name. I know that is problematic.

heinzel stored the SSH user per host but no port, so a host on 2222 only
worked if ~/.ssh/config carried the port, and that file is often not
maintained per host. The port is a fact of the host, so it goes into
server memory as "- SSH port:", shared in team mode, and every call adds
-o Port=, which ssh, scp, sftp and rsync -e all accept.

On first contact heinzel first asks known_hosts, without a connection,
whether the user accepted the host key earlier on another port.
If port 22 refuses, it tries the user's own "Alternative SSH ports:"
from memory/user.md, once each and at most three, then asks and offers
to add the answer to that list. There is no built-in list of common
ports: several ports in a row look like a scan to IPS rules, and a
timeout on 22 never leads to trying others.

Alias detection now needs the same address and port, since one NATed
address can reach several hosts. The firewall rules point out that "ufw
allow OpenSSH" and firewalld's ssh service open 22 only, and name the
port sshd listens on as the one to keep open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jpawlowski

Copy link
Copy Markdown
Contributor Author

The choices behind this, in case one of them looks odd:

Port in server memory, user name in user.md. The user name is personal and stays out of shared memory; the port is a fact of the host that every team member needs. With team mode on, the port travels with memory/servers/.

~/.ssh/config is not the answer. It works when it carries the port, and heinzel leaves it alone then. But on many workstations it is not maintained per host, and a teammate's copy says nothing about mine.

No list of "common" ports. Beyond 2222 there is no real consensus, and trying five ports in a row on one host looks like a port scan to psad, portsentry and provider IPS. Only ports the user runs sshd on themselves are tried, once each, at most three.

Only on Connection refused. Refused means the host is up and nothing listens on 22. A timeout usually means the host is down or a filter blocks us, often because of our own connections; more attempts keep that block alive. So a timeout goes straight to the question.

A login, not just an answer. A Permission denied on 2222 can come from a Gitea or Forgejo container's SSH, not from the host's sshd. Only a successful login stores the port.

2222 in the picker, not in the probe. It is offered as a choice when heinzel asks, never tried unasked.

Firewall. ufw allow OpenSSH and firewalld's ssh service open 22 only, so on a host with sshd elsewhere the current rules lock heinzel out. The rules now point to the ports sshd -T lists, all of them, because the connect port can differ behind NAT.

An AI agent wrote this text in my name. I know that is problematic.

The fleet audit names the port where the SSH call is built (step 3), and
the README gets its own short section instead of editing the shared
list of memory files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wintermeyer added a commit that referenced this pull request Sep 19, 2026
The firewall recipes allowed OpenSSH or firewalld's ssh service, which open port 22 only, so enabling a firewall on a host whose sshd listens elsewhere locked heinzel out. CLAUDE.md now has the agent read the ports from sshd -T, ListenAddress included, before enabling or tightening a firewall, and the family files point there instead of repeating the probe. Remembering a port per server (#15) is a separate feature and stays out of this change.

An AI agent wrote this text in my name. I know that is problematic.
# Conflicts:
#	.claude/skills/heinzel-fleet-audit/references/probes.md
jpawlowski and others added 2 commits September 19, 2026 23:20
Alias detection now resolves a name through ssh -G, so a host whose port
comes from ~/.ssh/config has no "- SSH port:" line and compared as 22.
The port of either side is the named one, else the memory line, else the
port ssh -G prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…at/remember-ssh-port

# Conflicts:
#	CLAUDE.md
#	rules/debian.md
#	rules/rhel.md
#	rules/suse.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remember an SSH port other than 22 per server

1 participant