Disclosure: I’m a contributor to CiSSHGo and use it in my own projects. I’ll be straightforward about what it does well and where it falls short.

You need to test your network automation code. Maybe it’s an Ansible playbook that configures interfaces across a thousand switches. Maybe it’s a Nornir script that collects show version from every device in your inventory. Maybe it’s a REST API that proxies SSH commands to network equipment.

The standard answer is “spin up a lab.” CML, EVE-NG, GNS3, ContainerLab — all solid tools that run actual NOS images. They give you real device behavior, real protocol interactions, real forwarding planes. They’re also heavy. A single Cisco CSR1000v image wants 4GB of RAM. Multiply that by the number of devices in your test topology and you’re looking at serious compute just to validate that your automation sends the right commands and parses the output correctly.

For a lot of automation testing, you don’t need a real forwarding plane. You need something that accepts an SSH connection, presents a prompt, and responds to show version with the right output. That’s the problem CiSSHGo solves.

What CiSSHGo Is (and Isn’t)

CiSSHGo is a single Go binary that spawns SSH listeners, each emulating a network device by playing back pre-defined command transcripts. You define what commands a device supports and what output it returns, and CiSSHGo handles the SSH session, prompt rendering, command matching, and context transitions (>#(config)#(config-if)#).

It is not a network simulator. There’s no forwarding plane, no routing protocol adjacencies, no MAC address table. It doesn’t run IOS or EOS or Junos. It plays back text files. That constraint is also its strength: a single CiSSHGo process can spawn thousands of SSH listeners using Go’s goroutine-per-listener model, each consuming a few kilobytes of memory. Try that with a Python-based mock server or a fleet of virtual routers.

CiSSHGo isn’t new. Tony Nealon (tbotnz, also the creator of netpalm) first published it in August 2020, and it’s been in active use since. What is new is the scope of recent work: the project went from a single-platform proof of concept to a v1.0.0 stable release in March 2026 with multi-vendor support, an inventory system, scenario mode, and proper release engineering. That’s enough of a capability jump to warrant a re-introduction for anyone who hasn’t looked at it recently. It’s MIT licensed, ships as pre-built binaries for Linux/macOS/Windows (amd64 and arm64), and publishes multi-arch Docker images to GitHub Container Registry.

Capabilities

Seven Platforms Out of the Box

CiSSHGo ships with transcript libraries for seven platforms: Cisco CSR1000v, IOS, IOS-XR, ASA, NX-OS, Arista EOS, and Juniper Junos. Each platform comes with show version, show ip interface brief (or equivalent), and show running-config transcripts sourced from NTC Templates test fixtures.

The transcript map is a YAML file that defines everything about a platform — hostname, credentials, supported commands, context hierarchy, and prompt format:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
platforms:
  csr1000v:
    vendor: "cisco"
    hostname: "cisshgo1000v"
    username: "admin"
    password: "admin"
    command_transcripts:
      "show version": "cisco/csr1000v/show_version.txt"
      "show ip interface brief": "cisco/csr1000v/show_ip_interface_brief.txt"
      "show running-config": "cisco/csr1000v/show_running-config.txt"
      "terminal length 0": "generic_empty_return.txt"
    context_hierarchy:
      "(config-if)#": "(config)#"
      "(config)#": "#"
      "#": ">"
      ">": "exit"
    context_search:
      "interface": "(config-if)#"
      "configure terminal": "(config)#"
      "enable": "#"
      "base": ">"

Adding a new platform or new commands is just adding text files and YAML entries. No Go code required.

Inventory Mode

For testing against multiple device types, CiSSHGo supports an inventory file that spawns different platforms on different ports from a single process:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
devices:
  - platform: csr1000v
    count: 10
  - platform: ios
    count: 10
  - platform: nxos
    count: 10
  - platform: eos
    count: 10
  - platform: junos
    count: 10

One binary, 50 SSH listeners, five different platform personalities. Each gets its own hostname, credentials, prompt style, and command set.

Inventory mode: mixed platforms and scenarios from a single binary

Scenario Mode

This is the feature that moved CiSSHGo from “useful for smoke tests” to “useful for real integration testing.” A scenario defines an ordered sequence of commands with different outputs at each step. The classic use case: show running-config returns one output before a configuration change and different output after.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
scenarios:
  csr1000v-add-interface:
    platform: csr1000v
    sequence:
      - command: "enable"
        transcript: "generic_empty_return.txt"
      - command: "show running-config"
        transcript: "scenarios/csr1000v-add-interface/running_config_before.txt"
      - command: "configure terminal"
        transcript: "generic_empty_return.txt"
      - command: "interface GigabitEthernet0/0/2"
        transcript: "generic_empty_return.txt"
      - command: "ip address 172.16.0.1 255.255.255.0"
        transcript: "generic_empty_return.txt"
      - command: "no shutdown"
        transcript: "generic_empty_return.txt"
      - command: "end"
        transcript: "generic_empty_return.txt"
      - command: "show running-config"
        transcript: "scenarios/csr1000v-add-interface/running_config_after.txt"

Each SSH session gets its own sequence pointer. Commands that don’t match the current step fall through to the platform’s normal command set. Scenario mode also handles interface abbreviation matching — int g0/0/2 matches interface GigabitEthernet0/0/2 without a hardcoded abbreviation table. The sequence step itself provides the ground truth.

Flexible Prompts

Not every NOS uses the Cisco hostname# prompt format. CiSSHGo supports a prompt_format field for platforms like Junos that use user@hostname> style prompts, and context_prefix_lines for multi-line prompts like Junos’s [edit] prefix:

1
2
3
4
5
6
7
  junos:
    vendor: "juniper"
    hostname: "cisshgo-junos"
    username: "admin"
    prompt_format: "{username}@{hostname}{context}"
    context_prefix_lines:
      "#": "[edit]"

This produces:

1
2
3
4
admin@cisshgo-junos>
admin@cisshgo-junos> configure
[edit]
admin@cisshgo-junos#

Session Behavior

Once a listener is running, CiSSHGo handles the session the way you’d expect from a real device. Commands are matched by prefix — sh ver resolves to show version, sh ip int br to show ip interface brief — and ambiguous abbreviations get the familiar % Ambiguous command error. Your automation code can use the same shortened commands it uses against real hardware.

Both interactive shell sessions and exec mode (ssh host "show version") work. Automation tools like Ansible’s network_cli connection plugin use exec mode for command execution, so this matters for realistic testing.

Transcript files support Go’s text/template syntax. You can reference device fields like {{.Hostname}} in your transcript output, so the same transcript file produces different output for different devices in an inventory.

Interactive session with abbreviated command matching and Go template variables

Running It

Binary:

1
2
3
4
5
6
7
8
# Default: 50 listeners starting at port 10000
./cisshgo

# Single listener on port 10022
./cisshgo --listeners 1 --starting-port 10022

# Multi-device inventory
./cisshgo --inventory inventory.yaml

Docker:

1
docker run -d -p 10000-10049:10000-10049 ghcr.io/tbotnz/cisshgo:latest

Every CLI flag has a corresponding environment variable (CISSHGO_LISTENERS, CISSHGO_STARTING_PORT, CISSHGO_TRANSCRIPT_MAP, CISSHGO_PLATFORM, CISSHGO_INVENTORY), so it drops into a docker-compose or Kubernetes manifest without wrapper scripts.

Where It Fits

Scale Testing

This is where CiSSHGo’s architecture pays off. If you’re building or evaluating a network automation framework that needs to handle thousands of devices, you need thousands of SSH endpoints to test against. You don’t need those endpoints to actually route packets — you need them to accept connections, authenticate, and return plausible output.

A single CiSSHGo process with --listeners 10000 spawns ten thousand SSH listeners, each running as a goroutine. Memory overhead is measured in megabytes, not gigabytes. Compare that to spinning up ten thousand virtual routers (not happening), ten thousand containers running a Python SSH server (possible but expensive), or trying to test at scale against a lab of 20 real devices and hoping the math extrapolates (it won’t — connection pooling, queue depth, and timeout behavior all change at scale).

Pair CiSSHGo with an inventory file and you get a mixed-vendor topology: a few thousand IOS devices, a few thousand NX-OS, some EOS, some Junos. Your automation framework sees what looks like a heterogeneous production network. The responses are canned, but the SSH handshakes, authentication, and session management are real.

CI/CD Pipeline Testing

The most straightforward use case. Drop CiSSHGo into your CI pipeline as a service container, point your automation tests at it, and validate that your code sends the right commands and parses the output correctly. No lab infrastructure to maintain, no NOS licenses to manage, no flaky device VMs timing out mid-test.

A docker-compose snippet for a GitHub Actions workflow:

1
2
3
4
5
6
7
services:
  cisshgo:
    image: ghcr.io/tbotnz/cisshgo:v1.0.0
    command: ["--listeners", "2", "--starting-port", "10022"]
    ports:
      - "10022:10022"
      - "10023:10023"

Your test suite connects to localhost:10022, runs commands, and asserts on the output. The CiSSHGo container starts in under a second.

Integration Test Stacks

I use CiSSHGo as the mock SSH backend in the integration test suite for NAAS (Netmiko As A Service). The docker-compose stack spins up CiSSHGo alongside the API server, workers, and Redis, and the tests exercise the full request path: HTTP request → job queue → Netmiko SSH connection to CiSSHGo → response parsing → result delivery. The CiSSHGo container handles send_command, send_config, structured output parsing, platform autodetect, and authentication failure scenarios — all without a real network device in the loop. I’ll cover that setup in detail in a future post.

Parser Development

If you’re writing or testing TextFSM or TTP templates — something I’ve written about before — you need consistent, known-good command output to parse against. CiSSHGo’s transcripts are sourced from NTC Templates test fixtures, so you get realistic output that matches what the parsing community already validates against. Point your parser at CiSSHGo, iterate on your template, and know that the input is stable between runs.

Demo and Training Environments

Need to demo an automation tool without access to a lab? Need a training environment where students can SSH into “devices” without risk? CiSSHGo gives you a multi-vendor topology from a single container. It won’t teach anyone OSPF, but it will let them practice writing Ansible playbooks against something that responds like a real switch.

When Not to Use It

Be clear-eyed about the limitations:

  • No protocol simulation. If your tests depend on BGP adjacencies forming, OSPF routes being installed, or ARP tables populating, CiSSHGo can’t help. You need CML, ContainerLab, or real hardware.
  • No NETCONF, gNMI, or SNMP. CiSSHGo is SSH-only. If your automation uses model-driven interfaces, look elsewhere.
  • No dynamic state beyond scenarios. Outside of scenario mode, every session gets the same output for the same command. There’s no simulated interface going up or down, no counter incrementing. Scenario mode adds ordered state changes, but it’s scripted, not reactive.
  • Transcript maintenance. Your transcripts need to match what your automation expects. If a real device’s show version output changes between NOS versions and your parser depends on the new format, you need to update the transcript. This is manageable but not zero-effort.

The honest framing: CiSSHGo tests your automation’s SSH interaction layer — connection handling, command dispatch, output parsing, error handling. It doesn’t test whether your automation produces correct network state. Those are different problems, and you probably need both kinds of testing. Use CiSSHGo for the first, a real lab for the second.

Getting Started

The GitHub repo has pre-built binaries on the releases page, Docker images on GHCR, and documentation covering configuration, transcript authoring, and the inventory system. The project is MIT licensed and accepts contributions — the CONTRIBUTING.md covers the workflow.

If you’re already testing network automation against real devices or heavyweight simulators and finding it slow, flaky, or expensive to maintain, CiSSHGo is worth a look. It won’t replace your lab, but it might mean you only need the lab for the tests that actually require one.