Open-source project
moul/assh avatar
moul/assh

moul/assh: a regex-aware wrapper that regenerates your ~/.ssh/config

:computer: make your ssh client smarter

3,227 stars163 forksGoMIT

At a glance

What is it?
assh sits between your ssh client and OpenSSH, adding regex host matching, gateways, templates and inheritance to a YAML file, then writing the result back out as a plain ~/.ssh/config. Useful if your host list has outgrown hand-editing; unnecessary if you have three servers.
Who is it for?
Adopt assh if you maintain a host list large enough that OpenSSH wildcards and hand-written ProxyCommand lines have stopped being readable, and you are willing to let a generator own ~/.ssh/config. Do not adopt it if you have a handful of hosts, or if you need per-host rollback of that file: the README documents regeneration, not a backup or revert path.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem assh solves, and who actually has it

OpenSSH configuration is a flat list of Host blocks. That is fine for ten machines. It stops being fine when the same host appears in several contexts (behind the office gateway, reachable directly from home), when a fleet shares a common prefix and a common set of options, or when you want one short name for a long hostname. OpenSSH has some of this: v7.3 added native Include support, and the README points that out directly, so splitting configuration across files is no longer a reason to install anything. What OpenSSH does not give you is pattern matching beyond wildcards, inheritance between hosts, or a way to describe a chain of jump hosts once and reuse it.

assh targets the person who keeps that list in a file and edits it by hand. The README describes it as a transparent wrapper that adds regex, aliases, gateways, dynamic hostnames, graphviz and JSON output to SSH. The audience is narrow on purpose: people running fleets, lab machines, or the same service across several environments, who want the generated ~/.ssh/config to be a build artifact rather than something they type. If you connect to three servers, the YAML layer is pure overhead.

How assh works: YAML in, ~/.ssh/config out

assh is not an SSH client. The README says lib-ssh wraps assh as a ProxyCommand, which is why ssh, scp, rsync, git and desktop applications built on lib-ssh keep working without knowing assh exists. The core loop is a generator: you write assh.yml, assh reads it, and it writes ~/.ssh/config. The README states it automatically regenerates that file when needed, so the generated file is not meant to be edited.

Gateways are the feature that carries the most weight. In the configuration example, hostc declares Gateways: hostb and hostb declares Gateways: hosta, and assh resolves the chain into nested ProxyCommand lines. The command-line form is shorter: ssh hosta/hostb means connect to hosta through hostb, and ssh hosta/hostb/hostc adds a second hop. The README gives the expansion of each so you can see exactly what OpenSSH receives.

The fallback behaviour is the part worth reading twice. A host can list several gateways, and the example uses direct first, then hosta, with GatewayConnectTimeout: 2. assh tries the direct connection, and if it fails within the timeout, falls back to the gateway. That is a real design choice: fast on the office network, still working from outside it, at the cost of a two-second stall whenever the direct path is blocked rather than refused.

Two smaller mechanisms matter in practice. Hooks fire at defined points: BeforeConnect runs before assh tries the remote SSH port and, per the README, fires once per gateway until one succeeds; OnConnect fires once the TCP connection is up and, the README notes, is not aware of the authentication process. Both receive Go template variables such as {{.Host.Name}} and {{.Host.Port}}. Separately, ControlMasterMkdir: true creates ControlPath directories so you can use slashes in ControlPath, which OpenSSH itself will not do for you.

Installing assh and building your first config

The repository ships a Dockerfile whose runtime stage is alpine:3.24 with the binary at /bin/assh and ENTRYPOINT ["/bin/assh"], and the Makefile defines a generate target that runs go generate before install, unit tests, build and lint. The README does not include a package-manager install section, so the two paths visible in the repository are building from source and running the container. From a checkout, go install builds the command; the Makefile's GO_INSTALL_OPTS shows the version and VcsRef linker flags it sets, which tells you the binary reports its build metadata.

Start with a config file. This is the gateway chain from the README, reduced to two hosts:

yaml
hosts:
  hosta:
    Hostname: 1.2.3.4

  hostb:
    Hostname: 5.6.7.8
    Gateways: hosta

With that in place, ssh hostb should reach 5.6.7.8 by way of 1.2.3.4, which the README expresses as the equivalent of ssh -o ProxyCommand="ssh hostb nc %h %p" hosta. The same file can be used from the command line without any configuration at all: ssh hosta/hostb asks for the same route. Use that form first, because it confirms the gateway works before you commit anything to disk.

The Makefile shows how the project inspects its own examples, and it is the check worth copying. For each examples/*/assh.yml it runs config build into an ssh_config file, then config graphviz into a .dot file rendered with dot -Tpng. Running the build step against your own YAML and reading the generated ssh_config is the only way to know what assh will hand to OpenSSH:

bash
assh -c assh.yml config build > ssh_config
assh -c assh.yml config graphviz > graphviz.dot

The second command is the one people forget. Gateways form a graph, and a mis-declared chain is much easier to see as a picture than as nested ProxyCommand lines.

Where assh gets in the way

The generated ~/.ssh/config is the main risk. assh owns that file and rewrites it. The README documents regeneration but does not document a backup, a merge, or a rollback path. If you have hand-written Host blocks in ~/.ssh/config today, moving to assh means deciding what happens to them, and the documentation does not answer that for you. Treat the first run as a migration, not an install.

Gateway fallback has a cost the README states plainly: GatewayConnectTimeout: 2 means a two-second wait before assh gives up on the direct route. On a network that blackholes traffic rather than rejecting it, every connection pays that. The README frames the fallback as a way to get the best performance when possible while still working outside the company, which is accurate, but it is a trade you are making per host, not a free win.

OnConnect is easy to misread. The README says it is not aware of the authentication process and will always be raised, so it is a TCP-level event, not a login event. Anything you hang off it runs whether or not the user authenticates successfully. BeforeConnect has the opposite shape: with multiple gateways it fires for each one until a connection succeeds, so a hook with side effects can run more than once per logical connection.

Finally, the wrapper only helps tools that go through ssh or lib-ssh. The README lists the integrations it supports, and they are all in that family. An application with its own SSH stack, or a workflow built on a cloud provider's session manager, gets nothing from assh.

assh against plain OpenSSH and ssh config generators

The honest alternative is OpenSSH itself. Since v7.3, Include lets you split configuration across files, which the README acknowledges when it lists includes as a feature. Wildcards and per-host overrides cover a lot of the aliasing people want. If your configuration is already readable with Include plus Host patterns, adding a YAML layer and a generator buys you regex, inheritance, templates and graphviz, and costs you a build step and a file you no longer edit by hand.

The other category is tools that generate ssh config from an external source of truth, such as an inventory or a cloud API. The difference is where the truth lives. assh's input is a file you maintain, and its output is ~/.ssh/config; it does not discover hosts. That makes it predictable and offline, and it means the host list is still yours to keep current.

Maintenance, releases and the MIT licence

The last push to the repository was on 2026-09-20, and the most recent release, v2.17.3, is dated 2026-07-24, following v2.17.2 on 2026-05-12 and v2.17.1 on 2026-03-06. That is a steady cadence of tagged releases, and the module path moul.io/assh/v2 with go 1.25.0 in go.mod means the Go toolchain floor moves with the project. Upgrading is a binary swap plus, if the schema changed, a config edit; the YAML you write is the upgrade surface, and the generated ~/.ssh/config follows from it.

assh is MIT licensed. That permits commercial and internal use with minimal conditions, but the repository does not state a policy on contributions or on what happens to the project if the maintainer stops. If you adopt it, the dependency you are taking on is a generator whose output you can inspect and keep, which is a better position than a runtime you cannot replace.

Editorial conclusion

Adopt assh if you maintain a host list large enough that OpenSSH wildcards and hand-written ProxyCommand lines have stopped being readable, and you are willing to let a generator own ~/.ssh/config. Do not adopt it if you have a handful of hosts, or if you need per-host rollback of that file: the README documents regeneration, not a backup or revert path. Before committing, run assh config build against your existing config and diff the output against the ~/.ssh/config you use today.

Frequently asked questions

What is the moul/assh config file, and where does it live?

It is a YAML file, shown in the README as assh.yml, describing hosts with keys such as Hostname and Gateways. Commands take it explicitly with assh -c assh.yml, as the Makefile's examples target does.

Can I use moul/assh on Ubuntu?

The repository does not document a package-manager install, so the visible paths are building from source with the Go toolchain or running the published container image, which is based on alpine:3.24 and has /bin/assh as its entrypoint. The binary itself is built for Linux, OpenBSD and macOS targets in the Makefile's release target.

How do I connect through a gateway with moul/assh?

From the command line, ssh hosta/hostb connects to hosta through hostb, and ssh hosta/hostb/hostc adds a second hop. The same route can be declared in the config with Gateways, and the README gives the equivalent ProxyCommand for each form.

Does moul/assh replace my ~/.ssh/config?

It regenerates it. The README lists automatic regeneration of ~/.ssh/config as a feature, so the generated file is output rather than something you edit. The README does not document a backup or rollback path for that file.

Official sources

  1. License: MIT
  2. moul/assh on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/moul-assh.svg)](https://hysenlabs.com/projects/moul-assh)