SFTP is a standard protocol for secure file transfer, but working with it in Go isn’t always straightforward. The libraries are low-level, examples are scattered, and small mistakes in SSH setup or host key handling will stop you immediately.
This guide shows how to connect to an SFTP server in Go using github.com/pkg/sftp and golang.org/x/crypto/ssh, then build out the core operations: listing files, uploading files, and downloading files.
By the end, you’ll have a working Go SFTP implementation you can drop into real workflows with your eyes closed!
Requirements for connecting to SFTP in Go
As always, preparation comes first. You’ll need access to an SFTP server and the Go libraries required to establish an SSH connection and SFTP session.
If you don't have one, you can set up an SFTP endpoint on SFTP To Go in less than 30 seconds.
The libraries github.com/pkg/sftp and golang.org/x/crypto/ssh are needed to connect and interact with an SFTP server. When you are ready to install them, manually run:
$ go get github.com/pkg/sftp
$ go get golang.org/x/crypto/sshThese two libraries handle different parts of the stack: ssh manages authentication and transport, while sftp provides file operations on top of that session.
Or create a go.mod file and declare your dependencies in it. go.mod files define Go modules, which, amongst other things, are used to add dependencies to other Go modules. Save the following as go.mod:
module sftptogo.com/examples/go
go 1.13
require (
github.com/pkg/sftp v1.12.0
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
)go.mod
Connecting to SFTP in Go using SSH
In this example, the connection details are stored in an environment variable named SFTPTOGO_URL, formatted as:
sftp://user:password@host
The code parses this value to extract credentials, sets up SSH authentication (including optional agent support), and validates the server using a host key from the known_hosts file.
Once the SSH connection is established, the SFTP client is initialized and assigned to sc, which is used for all file operations.
package main
import (
"fmt"
"io"
"os"
"net"
"net/url"
"bufio"
"strings"
"path/filepath"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
)
func main() {
// Get SFTP To Go URL from environment
rawurl := os.Getenv("SFTPTOGO_URL")
parsedUrl, err := url.Parse(rawurl)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse SFTP To Go URL: %s\n", err)
os.Exit(1)
}
// Get user name and pass
user := parsedUrl.User.Username()
pass, _ := parsedUrl.User.Password()
// Parse Host and Port
host := parsedUrl.Host
// Default SFTP port
port := 22
hostKey := getHostKey(host)
fmt.Fprintf(os.Stdout, "Connecting to %s ...\n", host)
var auths []ssh.AuthMethod
// Try to use $SSH_AUTH_SOCK which contains the path of the unix file socket that the sshd agent uses
// for communication with other processes.
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
// Use password authentication if provided
if pass != "" {
auths = append(auths, ssh.Password(pass))
}
// Initialize client configuration
config := ssh.ClientConfig{
User: user,
Auth: auths,
// Uncomment to ignore host key check
//HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
addr := fmt.Sprintf("%s:%d", host, port)
// Connect to server
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to [%s]: %v\n", addr, err)
os.Exit(1)
}
defer conn.Close()
// Create new SFTP client
sc, err := sftp.NewClient(conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to start SFTP subsystem: %v\n", err)
os.Exit(1)
}
defer sc.Close()
}
// Get host key from local known hosts
func getHostKey(host string) ssh.PublicKey {
// parse OpenSSH known_hosts file
// ssh or use ssh-keyscan to get initial key
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read known_hosts file: %v\n", err)
os.Exit(1)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing %q: %v\n", fields[2], err)
os.Exit(1)
}
break
}
}
if hostKey == nil {
fmt.Fprintf(os.Stderr, "No hostkey found for %s", host)
os.Exit(1)
}
return hostKey
}connect.go
Listing files on an SFTP server in Go
Now that the connection is established, we can start interacting with the remote server. Listing files is usually the first useful operation, because it confirms that authentication worked, the session is active, and the target directory is accessible.
In this example, the listFiles function takes the SFTP client sc and a remote directory path, then calls ReadDir to retrieve the directory contents. It prints the file name, modification time, and size for each entry on the server.
func listFiles(sc sftp.Client, remoteDir string) (err error) {
fmt.Fprintf(os.Stdout, "Listing [%s] ...\n\n", remoteDir)
files, err := sc.ReadDir(remoteDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to list remote dir: %v\n", err)
return
}
for _, f := range files {
var name, modTime, size string
name = f.Name()
modTime = f.ModTime().Format("2006-01-02 15:04:05")
size = fmt.Sprintf("%12d", f.Size())
if f.IsDir() {
name = name + "/"
modTime = ""
size = "PRE"
}
// Output each file name and size in bytes
fmt.Fprintf(os.Stdout, "%19s %12s %s\n", modTime, size, name)
}
return
}listfiles.go
Uploading a file to SFTP in Go
The next step is to upload a file to the remote server.
The uploadFile function takes three arguments: the SFTP client, the path to the local file, and the destination path on the server. It opens the local file, creates any missing remote directories along the path, then writes the file to the remote location.
A call to the function looks like this: uploadFile(*sc, "./local.txt", "./remote.txt")
This is the part of the workflow that turns a connection into something useful for application jobs, exports, reports, and automated transfers.
// Upload file to sftp server
func uploadFile(sc sftp.Client, localFile, remoteFile string) (err error) {
fmt.Fprintf(os.Stdout, "Uploading [%s] to [%s] ...\n", localFile, remoteFile)
srcFile, err := os.Open(localFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open local file: %v\n", err)
return
}
defer srcFile.Close()
// Make remote directories recursion
parent := filepath.Dir(remoteFile)
path := string(filepath.Separator)
dirs := strings.Split(parent, path)
for _, dir := range dirs {
path = filepath.Join(path, dir)
sc.Mkdir(path)
}
// Note: SFTP To Go doesn't support O_RDWR mode
dstFile, err := sc.OpenFile(remoteFile, (os.O_WRONLY|os.O_CREATE|os.O_TRUNC))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open remote file: %v\n", err)
return
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to upload local file: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%d bytes copied\n", bytes)
return
}uploadfile.go
Downloading a file from SFTP in Go
We’re almost done! We just need to download our files. Downloading works in the opposite direction. The downloadFile function takes the SFTP client, the path to the remote file, and the local path where the file should be saved.
It opens the remote file through the SFTP session, creates the local file, and copies the contents from one side to the other. A function call would look like this: downloadFile(*sc, "./remote.txt", "./download.txt")
This is the pattern you would use when pulling reports, partner files, generated data, or other remote outputs into a Go application.
// Download file from sftp server
func downloadFile(sc sftp.Client, remoteFile, localFile string) (err error) {
fmt.Fprintf(os.Stdout, "Downloading [%s] to [%s] ...\n", remoteFile, localFile)
// Note: SFTP To Go doesn't support O_RDWR mode
srcFile, err := sc.OpenFile(remoteFile, (os.O_RDONLY))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open remote file: %v\n", err)
return
}
defer srcFile.Close()
dstFile, err := os.Create(localFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open local file: %v\n", err)
return
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to download remote file: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%d bytes copied\n", bytes)
return
}downloadfile.go
The full (Golang) Go SFTP example
So we’ve made it to the end! At this point, we have the full workflow in place: connect, list files, upload, download, and close the session cleanly. If you want to run the entire example from start to finish, save the following code as main.go:
package main
import (
"fmt"
"io"
"os"
"net"
"net/url"
"bufio"
"strings"
"path/filepath"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/pkg/sftp"
)
func main() {
// Get SFTP To Go URL from environment
rawurl := os.Getenv("SFTPTOGO_URL")
parsedUrl, err := url.Parse(rawurl)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse SFTP To Go URL: %s\n", err)
os.Exit(1)
}
// Get user name and pass
user := parsedUrl.User.Username()
pass, _ := parsedUrl.User.Password()
// Parse Host and Port
host := parsedUrl.Host
// Default SFTP port
port := 22
hostKey := getHostKey(host)
fmt.Fprintf(os.Stdout, "Connecting to %s ...\n", host)
var auths []ssh.AuthMethod
// Try to use $SSH_AUTH_SOCK which contains the path of the unix file socket that the sshd agent uses
// for communication with other processes.
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
}
// Use password authentication if provided
if pass != "" {
auths = append(auths, ssh.Password(pass))
}
// Initialize client configuration
config := ssh.ClientConfig{
User: user,
Auth: auths,
// Uncomment to ignore host key check
//HostKeyCallback: ssh.InsecureIgnoreHostKey(),
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
addr := fmt.Sprintf("%s:%d", host, port)
// Connect to server
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to [%s]: %v\n", addr, err)
os.Exit(1)
}
defer conn.Close()
// Create new SFTP client
sc, err := sftp.NewClient(conn)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to start SFTP subsystem: %v\n", err)
os.Exit(1)
}
defer sc.Close()
//*
//* List working directory files
//*
listFiles(*sc, ".")
fmt.Fprintf(os.Stdout, "\n")
//*
//* Upload local file to remote file
//*
uploadFile(*sc, "./local.txt", "./remote.txt")
fmt.Fprintf(os.Stdout, "\n")
//*
//* Download remote file to local file
//*
downloadFile(*sc, "./remote.txt", "./download.txt")
fmt.Fprintf(os.Stdout, "\n")
}
func listFiles(sc sftp.Client, remoteDir string) (err error) {
fmt.Fprintf(os.Stdout, "Listing [%s] ...\n\n", remoteDir)
files, err := sc.ReadDir(remoteDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to list remote dir: %v\n", err)
return
}
for _, f := range files {
var name, modTime, size string
name = f.Name()
modTime = f.ModTime().Format("2006-01-02 15:04:05")
size = fmt.Sprintf("%12d", f.Size())
if f.IsDir() {
name = name + "/"
modTime = ""
size = "PRE"
}
// Output each file name and size in bytes
fmt.Fprintf(os.Stdout, "%19s %12s %s\n", modTime, size, name)
}
return
}
// Upload file to sftp server
func uploadFile(sc sftp.Client, localFile, remoteFile string) (err error) {
fmt.Fprintf(os.Stdout, "Uploading [%s] to [%s] ...\n", localFile, remoteFile)
srcFile, err := os.Open(localFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open local file: %v\n", err)
return
}
defer srcFile.Close()
// Make remote directories recursion
parent := filepath.Dir(remoteFile)
path := string(filepath.Separator)
dirs := strings.Split(parent, path)
for _, dir := range dirs {
path = filepath.Join(path, dir)
sc.Mkdir(path)
}
// Note: SFTP To Go doesn't support O_RDWR mode
dstFile, err := sc.OpenFile(remoteFile, (os.O_WRONLY|os.O_CREATE|os.O_TRUNC))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open remote file: %v\n", err)
return
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to upload local file: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%d bytes copied\n", bytes)
return
}
// Download file from sftp server
func downloadFile(sc sftp.Client, remoteFile, localFile string) (err error) {
fmt.Fprintf(os.Stdout, "Downloading [%s] to [%s] ...\n", remoteFile, localFile)
// Note: SFTP To Go doesn't support O_RDWR mode
srcFile, err := sc.OpenFile(remoteFile, (os.O_RDONLY))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open remote file: %v\n", err)
return
}
defer srcFile.Close()
dstFile, err := os.Create(localFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open local file: %v\n", err)
return
}
defer dstFile.Close()
bytes, err := io.Copy(dstFile, srcFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to download remote file: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%d bytes copied\n", bytes)
return
}
// Get host key from local known hosts
func getHostKey(host string) ssh.PublicKey {
// parse OpenSSH known_hosts file
// ssh or use ssh-keyscan to get initial key
file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read known_hosts file: %v\n", err)
os.Exit(1)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var hostKey ssh.PublicKey
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
if len(fields) != 3 {
continue
}
if strings.Contains(fields[0], host) {
var err error
hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing %q: %v\n", fields[2], err)
os.Exit(1)
}
break
}
}
if hostKey == nil {
fmt.Fprintf(os.Stderr, "No hostkey found for %s", host)
os.Exit(1)
}
return hostKey
}main.go
Finally, run it using the command:
go run main.goAll done! Congratulations on connecting to SFTP using Go! You now have a working Go SFTP example that covers the essential operations: connection setup, host key validation, directory listing, file upload, and file download.
From here, the same structure can be extended into scheduled jobs, internal tooling, ETL processes, and other workflows where secure file transfer needs to happen predictably and without manual handling.
Level-up your skills with some more code samples on GitHub. Explore SFTP automation to reduce human error and streamline workflows.
Where this Go SFTP example is useful
This example is a great fit if you need to connect to an SFTP server from Go, inspect remote directories, move files in either direction, or build file transfer into a backend job or integration flow.
It is especially useful as a starting point for automation, batch processing, and server-side workflows where SSH-based file transfer needs to be handled directly in code.
Related SFTP guides
- How to Connect to SFTP with Python: Upload, Download, & List
- How to connect to SFTP in Ruby on Rails
- How to connect to SFTP with C# using SSH.NET
- SFTP Commands Cheat Sheet
- What Is SFTP? How SFTP Works And When To Use It
Frequently asked questions
How do I connect to an SFTP server in Go?
You connect to an SFTP server in Go by creating an SSH connection with golang.org/x/crypto/ssh, then creating an SFTP client with github.com/pkg/sftp on top of that SSH session.
What Go libraries are used for SFTP?
This example uses github.com/pkg/sftp for SFTP file operations and golang.org/x/crypto/ssh for SSH authentication and transport.
How do I list files on an SFTP server in Go?
You can list files by calling ReadDir on the SFTP client and passing the remote directory path. In this example, the function prints the file name, modification time, and size for each entry.
How do I upload a file to SFTP in Go?
You upload a file by opening the local file, opening or creating the remote file through the SFTP client, and copying the contents with io.Copy.
How do I download a file from SFTP in Go?
You download a file by opening the remote file through the SFTP client, creating a local file, and copying the contents from the remote file to the local one with io.Copy.
What does SFTPTOGO_URL do in this Go example?
In this example, SFTPTOGO_URL stores the SFTP connection details in URI format. The code parses it to extract the host, username, and password used for the SSH connection. It is part of this example’s setup, not a Go requirement.
