Development

How to Build a VPS Health Monitoring CLI in Go: Practical Walkthrough

Learn how to build a VPS health monitoring CLI in Go — a practical project created by Christoph Berger. Straightforward, reliable, and built for real-world use.

is*hosting team 7 Oct 2025 6 min reading
How to Build a VPS Health Monitoring CLI in Go: Practical Walkthrough
Table of Contents

You have a fleet of VPSs — maybe five, maybe fifty — spread across global data centers. As VPS deployments scale, health monitoring becomes increasingly important. Which VPSs operate near their disk capacity? Which are busy, which are idle? Are any VPSs running short of network bandwidth?

Whether you are a DevOps engineer managing environments for a dozen projects, a solo developer with ten edge nodes, or an agency that sets up CMS servers for their clients, you need answers to these questions fast. Yet, you don’t want to deal with the massive overhead of running full-fledged monitoring software on all nodes.

This is where the is*hosting API shines. Detailed information about VPS statuses, configurations, and plans is readily available, among many more actions for VPSs, dedicated servers, storage, services, and VPNs.

In this article, you will learn how to build a command-line tool in Go to query your VPSs' statuses with a single API call, including the current CPU, disk, RAM, network usage, and uptime. Use it as a foundation for VPS health monitoring that you can extend and customize to your specific use case.

As prerequisites, you only need the following:

  • Working knowledge of Go (it won't get complicated)
  • An is*hosting account
  • At least one active VPS

Get Your VPS Ready

If you already have a VPS, feel free to skip this paragraph and move on to creating an API key. Otherwise, start by ordering a VPS.

Don’t worry, setting up one only takes a few easy steps: select the desired VPS plan and follow the order process through.

Once you're in the Client Area, open the API page through the left-hand menu and click on the Add API key button. Enter a name for your key ("default" is a good choice for the first key) and click "Save."

For the purpose of this how-to project, it is sufficient to save the key to an environment variable; for production-level code, using a secrets manager is highly recommended.

On your machine, save the API key to an environment variable called ISHOSTING_API_KEY. The test code we are going to build will refer to that name.

Now your is*hosting VPS is ready for being monitored. In the next section, we'll set up the Go project.

Set up the Go Project

server health monitoring in Go

Start the CLI tool project by creating a new, empty directory, cding into it, and running


go mod init <module-path>

where <module-path> can be something like github.com/yourusername/vps-monitor if you plan to share it later, or just vps-monitor if it stays on your machine.

Please ensure to use Go 1.21 or later, as this version adds the min() builtin that the following code uses. No worries, Go's backward compatibility promise makes staying up-to-date with the latest Go toolchain a breeze.

Then create an empty main.go file that we'll fill piecewise with code.

The first code to add includes package imports and a constant:


package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "log"
    "net/http"
    "net/url"
    "os"
    "text/tabwriter"
    "time"
)

const (
    baseURL = "https://api.ishosting.com"
)

Next, we define several types, starting with VPSList. This type represents the response of the API endpoint /vps/list. (More on this endpoint later.) Instead of writing all these types by hand, here is a neat shortcut: Fetch the JSON response from the API documentation of the /vps/list endpoint and use a tool like JSON-to-Go to turn it into a Go type definition.

For generating Go code from more, or all, endpoints of the is*hosting API, you might want to use the full is*hosting API specification with a tool like openapi-codegen or ogen.

This is the generated VPSList type—a slice of structures containing embedded structures:


type VPSList []struct {
    ID         string     `json:"id"`
    Name       string     `json:"name"`
    Tags       []string   `json:"tags"`
    Location   Location   `json:"location"`
    Plan       Plan       `json:"plan"`
    Platform   Platform   `json:"platform"`
    Status     Status     `json:"status"`
    CreatedAt  int        `json:"created_at"`
    UpdatedAt  int        `json:"updated_at"`
}

type Location struct {
    Name string `json:"name"`
    Code string `json:"code"`
}

type Plan struct {
    Name string `json:"name"`
    Code string `json:"code"`
}

type Platform struct {
    Name string `json:"name"`
    Code string `json:"code"`
}

type Status struct {
    Name     string  `json:"name"`
    Code     string  `json:"code"`
    Message  string  `json:"message"`
    Uptime   int     `json:"uptime"`
    CPU      CPU     `json:"cpu"`
    RAM      RAM     `json:"ram"`
    Drive    Drive   `json:"drive"`
    Network  Network `json:"network"`
    State    State   `json:"state"`
}

type CPU struct {
    Value   string `json:"value"`
    Message any    `json:"message"`
}

type RAM struct {
    Value   string `json:"value"`
    Message any    `json:"message"`
}

type Drive struct {
    Value   string `json:"value"`
    Message string `json:"message"`
}

type Network struct {
    Value   string `json:"value"`
    Message any    `json:"message"`
}

type State struct {
    Name    string `json:"name"`
    Code    string `json:"code"`
    Message any    `json:"message"`
}

For proper error handling, a new error type HTTPStatusError allows capturing the HTTP status as a code and a string, along with the response body that might contain additional error information.


type HTTPStatusError struct {
    Code   int    // e.g., 200
    Status string // e.g., "200 OK"
    Body   []byte
}

func (e HTTPStatusError) Error() string {
    return fmt.Sprintf("HTTP %s", e.Status)
}

From here, we can start implementing an API client for the /vps/list endpoint.

Send Authenticated GET Requests

VPS health monitoring with API

A basic API client is quickly defined:


type IshostingClient struct {
    baseURL string
    apiKey  string
    client  *http.Client
}

func NewIshostingClient(baseURL, key string) *IshostingClient {
    return &IshostingClient{
        baseURL: baseURL,
        apiKey:  key,
        client: &http.Client{
            Timeout: 30 * time.Second,
        },
    }
}

The IshostingClient type manages the base URL (in case it ever changes), the API key, and an embedded HTTP client, which is created with a 30-second timeout to prevent requests from blocking indefinitely.

A method Get() receives a query URL consisting of the base URL, the endpoint, and query parameters. It then sets the request header X-Api-Token to authenticate the request with an API key, and two other headers, Accept and User-Agent.

Here’s a gotcha: Some API security services mark Go's default User-Agent as "suspicious," so be sure to set a new, unique user agent.

The Get() method then sends the request, reads the response body, and returns the body or an HTTPStatusError if the HTTP status code indicates an error condition:


func (c *IshostingClient) Get(query string) (body []byte, err error) {
    req, err := http.NewRequest("GET", query, nil)
    if err != nil {
        return nil, fmt.Errorf("cannot create request: %w", err)
    }

    // set request headers for authentication and accepted format
    req.Header.Add("X-Api-Token", c.apiKey)
    req.Header.Add("Accept", "application/json")
    req.Header.Add("User-Agent", "go-ishosting-api-client/1.0")

    // send the request
    resp, err := c.client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("cannot send request: %w", err)
    }
    defer resp.Body.Close()

    // read the response
    body, err = io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("reading response failed: %w", err)
    }

    // check for HTTP errors
    if resp.StatusCode >= 400 {
        return nil, HTTPStatusError{
            Code:   resp.StatusCode,
            Status: resp.Status,
            Body:   body,
        }
    }

    return body, nil
}

This method already allows sending API requests to an is&ast;hosting endpoint, but it's not intended to be used directly; rather, we'll construct a specific method for calling the /vps/list endpoint.

Fetch VPS List and Status

VPS health monitoring

The /vps/list endpoint lists all VPSs in your portfolio along with valuable monitoring information, including the current CPU, RAM, and drive usage. Only a single API call is required to monitor all your VPSs!

The VpsList() method collects all this information and returns it as a VPSList. For best performance, the /vps/list endpoint breaks up large results into chunks, or “pages”, of data. By setting a limit parameter, you can tell the API how many VPS entries you want per page. To load more pages, simply send another request with an offset parameter that you increase by one for every subsequent page.

Rather than manually building URLs with string concatenation (which can be error-prone), we’ll use the net/url package to construct the query URL in a type-safe way:


func (c *IshostingClient) VpsList(limit, offset int) (list VPSList, err error) {
    // construct the query URL, starting with the base URL
    query, err := url.Parse(c.baseURL)
    if err != nil {
        return nil, fmt.Errorf("url.Parse: %w", err)
    }

    // add the endpoint
    query = query.JoinPath("/vps/list")

    // add query parameters
    params := url.Values{}
    params.Add("limit", fmt.Sprint(limit))
    if offset > 0 {
        params.Add("offset", fmt.Sprint(offset))
    }
    query.RawQuery = params.Encode()

    // pass the query URL to Get()
    body, err := c.Get(query.String())
    if err != nil {
        return nil, fmt.Errorf("GET /vps/list: %w", err)
    }

    // turn the returned JSON information into a VPSList slice
    err = json.Unmarshal(body, &list)
    if err != nil {
        return nil, fmt.Errorf("unmarshal VPS list: %w", err)
    }

With the two methods Get() and VpsList(), the code can retrieve all the information needed for monitoring VPSs; the remaining part is about displaying the results.

Display Data in the Terminal

server health monitoring

The /vps/list endpoint returns a JSON object, but the monitoring tool shall print a VPS list to the terminal. Function PrintVPSStats() achieves this with the help of the text/tabwriter package, which can translate text with tabbed columns into a properly aligned table:


func PrintVPSStats(vpsList VPSList) {
    w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
    defer w.Flush()

    // Print header
    fmt.Fprintln(w, "ID\tName\tLocation\tStatus Name\tStatus Message\tCPU\tRAM\tDrive\tNetwork\tState")
    fmt.Fprintln(w, "---\t---\t---\t---\t---\t---\t---\t---\t---\t---")

    // Print each VPS information
    for _, vps := range vpsList {
        fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
            vps.ID,
            vps.Name,
            vps.Location.Name,
            vps.Status.Name,
            vps.Status.Message,
            vps.Status.CPU.Value,
            vps.Status.RAM.Value,
            vps.Status.Drive.Value,
            vps.Status.Network.Value,
            vps.Status.State.Name,
        )
    }
}

Final Wiring and a First Test Run

VPS health monitoring

What's left to do is to:

  • fetch the API key from the environment variable set earlier
  • create an IshostingClient
  • call VpsList()
  • and print the response as a table

All of this can be done in main():


func main() {
    key := os.Getenv("ISHOSTING_API_KEY")
    if len(key) == 0 {
        log.Fatal("Please set ISHOSTING_API_KEY")
    }

    c := NewIshostingClient(baseURL, key)
    list, err := c.VpsList(10, 0)
    if err != nil {
        var httpErr HTTPStatusError
        if errors.As(err, &httpErr) {
            log.Fatalf("HTTP %s with body %s", httpErr.Status, string(httpErr.Body)[:min(len(httpErr.Body), 100)])
        }
        log.Fatalf("cannot list VPSs: %v", err)
    }

    PrintVPSStats(list)
}

The code is now ready to run! You can either copy and paste all of the above code linearly into your local main.go or fetch the full code from the Go Playground. (Note that the code won't work there because of the missing environment variable; also, you might want to refrain from pasting your API key there to avoid leaking it accidentally.)

Run the final code:


go run .

If you have booked, say, one VPS in Germany and one VPS in the Netherlands, your output would look like this:


ID           Name                 Location             Status Name  Status Message                CPU   RAM  Drive   Network      State
---          ---                  ---                  ---           ---                          ---   ---  ---     ---          ---
DE_12356212_3  HSE Group Servers  Germany              Running       Uptime: 0 Days, 18:01:34     0.1%  52%  19 Gb   ~34,5 Mb/s   Active
NL_12356212_3  HSE Group Servers  Nederland            Running       Uptime: 0 Days, 18:01:34     2.4%  34%  12 Gb   ~5,1 Mb/s    Active

Conclusion and Next Steps

Congratulations! With a few lines of code, you now have a basic VPS monitoring tool for your terminal. For further practice, consider implementing these enhancements:

  • A loop to retrieve information from more than 10 VPSs in batches of 10 VPSs at a time.
  • Colored terminal output.
  • A loop to update the data every n minutes and refresh the terminal.

This article walked you through building a Go client for the /vps/list endpoint. You're now equipped with the knowledge to call other endpoints and interact with your is*hosting setup programmatically.

VPS

Choose your perfect setup and enjoy everything a VPS can do.

From $5.94/mo