Guide to Implement an SSH Client Using Golang

English 简体中文 繁体中文 ภาษาไทย Tiếng Việt
Summary

Implementing an SSH client in Golang leverages the golang.org/x/crypto/ssh package, guiding developers through a series of steps. It begins by configuring connection parameters, including username and authentication methods, before establishing a connection to the remote server using ssh.Dial. Subsequently, a session is created, a pseudo-terminal is requested, and standard input/output streams are set up for interactive communication. The remote shell can then be started with session.Shell() or a specific command executed via session.Run(), with the program waiting for the session to conclude.

SSH, short for Secure Shell, is a network protocol used for securely remote logging into other computers on a network. I believe most backend developers are familiar with SSH. Common shell tools used for logging into servers, such as Xshell, SecureCRT, and iTerm2, are all based on the SSH protocol. In Golang, the crypto/ssh package provides functionality for implementing an SSH client. In this article, we will explain in detail how to implement an SSH client using Golang.

Creating SSH Client Configuration

First, we need to configure the parameters for the SSH client to connect to the server. The basic configuration includes the username, authentication method, and host key callback. Here is an example code snippet:

package main

import "golang.org/x/crypto/ssh"

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
}

In this configuration, the username is set to "username" and the password is set to "password". The ssh.Password function is used to create a password authentication method. The HostKeyCallback function is called each time a new host is connected to, and it is used to verify the server's host key. In this example, ssh.InsecureIgnoreHostKey is used, which means any host key is accepted. It is not recommended to use this in a production environment because not verifying the host key poses a security risk.

Connecting to the SSH Server

The ssh.Dial function is used to connect to the remote SSH server. It requires three parameters: the network type (usually "tcp"), the address and port of the server, and the configuration object created earlier. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}
}

Connect to the server with the IP address 192.168.3.111 on port 22 (the default port for SSH protocol). If the connection fails, an error will be returned, and you can use log.Fatal to print the error and exit the program.

Creating an SSH Session

Once the SSH connection is established, you can create an SSH session to communicate with the server. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()
}

Use the client.NewSession function to create a new session, and if it fails, an error will be returned. Use the defer keyword to ensure that the session is closed after the operations are completed.

Creating a Pseudo Terminal

The SSH protocol allows for the creation of a pseudo terminal, which simulates the behavior of a real terminal and allows for interactive commands, such as a shell or text editor. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}
	if err := session.RequestPty("linux", 80, 40, modes); err != nil {
		log.Fatal("request for pseudo terminal failed: ", err)
	}
}

Use the session.RequestPty function to request a pseudo terminal. It requires four parameters: the terminal type (in this case, "xterm"), the width and height of the terminal, and a map to set terminal modes. In this map, ssh.ECHO is set to 0, which disables echoing, and the input and output speeds are set to 14.4 kbaud.

Setting Input and Output

Specify the standard input and output for the remote shell. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
	"os"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}
	if err := session.RequestPty("linux", 80, 40, modes); err != nil {
		log.Fatal("request for pseudo terminal failed: ", err)
	}

	//set input and output
	session.Stdout = os.Stdout
	session.Stdin = os.Stdin
	session.Stderr = os.Stderr
}

Start Remote Shell

A remote shell is now ready, and you can use the session.Shell function to start a default shell or use the session.Run function to execute a specific command. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}
	if err := session.RequestPty("linux", 80, 40, modes); err != nil {
		log.Fatal("request for pseudo terminal failed: ", err)
	}
  
    // set input and output
	session.Stdout = os.Stdout
	session.Stdin = os.Stdin
	session.Stderr = os.Stderr

	if err := session.Shell(); err != nil {
		log.Fatal("failed to start shell: ", err)
	}
}

Start a default shell, and if it fails to start, an error will be returned.

Waiting for Session to End

Use the session.Wait function to block until the session ends. Here is an example code snippet:

package main

import (
	"golang.org/x/crypto/ssh"
	"log"
	"os"
)

func main() {
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", "192.168.3.111:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}

	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	modes := ssh.TerminalModes{
		ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}
	if err := session.RequestPty("linux", 80, 40, modes); err != nil {
		log.Fatal("request for pseudo terminal failed: ", err)
	}

	//set input and output
	session.Stdout = os.Stdout
	session.Stdin = os.Stdin
	session.Stderr = os.Stderr

	if err := session.Shell(); err != nil {
		log.Fatal("failed to start shell: ", err)
	}

	err = session.Wait()
	if err != nil {
		log.Fatal("Failed to run: " + err.Error())
	}
}

With this, you have completed the implementation of a basic SSH client. This client can connect to an SSH server, start a remote shell, and wait for the session to end.

Summary

The SSH client implemented in this article only provides the most basic functionality. To create a feature-rich and user-friendly SSH client, you need to pay attention to many details, such as error handling, reconnection, timeouts, signal handling, and more.

SSH GUIDE GOLANG SSH CLIENT

  RELATED

  COMMENT

1
PiterWeb
Aug 7, 2026 at 11:53 am

Thank you for the article, really cool. It helped me a lot in my current project RequestPty makes the process a lot easier