Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →A Go HTTP server is a handler, a router, and a listener: handlers write responses, a http.ServeMux selects a handler for each request, and http.Server accepts connections and applies operational settings. For a quick local demo, http.ListenAndServe is enough. For a service that needs timeouts, request-size limits, and graceful shutdown, configure an http.Server explicitly.
Build a minimal server with an explicit mux
This example uses Go’s standard library and works with current Go releases. It registers one route on a separately constructed mux, then listens on port 8080:
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte("Hello, Go!n"))
})
log.Println("listening on http://localhost:8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
Save it as main.go and run go run . from the module directory, or run go run main.go. Visit http://localhost:8080/; the response body should be Hello, Go!. The handler receives a http.ResponseWriter to set headers and write the response, and a *http.Request containing request details.
ListenAndServe blocks while it serves requests. It returns a non-nil error if it cannot start or stops unexpectedly, so startup code should not silently ignore that error. In this minimal version, log.Fatal records it and exits. For a service that needs controlled shutdown, use an http.Server and distinguish its expected shutdown error from genuine failures.
Recommended Free Tools
#1 Best Overall
Why pass a mux explicitly?
http.NewServeMux() creates a router whose registered routes are visible in the program. Passing nil as the handler to ListenAndServe instead selects the package-level http.DefaultServeMux. That can be convenient for tiny programs, but explicit routing avoids hidden global registrations and makes the server’s route dependencies easier to follow.
Choose between a convenience call and a configured server
The package function is concise; an http.Server gives you named settings for request handling and lifecycle management.
| Approach | Use it when | What you control |
|---|---|---|
http.ListenAndServe(addr, handler) |
You need a small local example or a simple server with default settings. | Address and handler. It uses the package’s server defaults. |
http.Server{...}.ListenAndServe() |
You need to set timeouts, header limits, or coordinate shutdown. | Address, handler, timeout fields, header limit, and server lifecycle. |
There is no single correct timeout configuration for every application. A short page-rendering service, a streaming endpoint, and a large-upload API have different needs. Start with the behavior your service permits, then configure and test to that policy rather than copying values blindly.
Configure request timeouts and header size deliberately
This server illustrates the relevant fields and their distinct purposes. The values shown below are examples, not universal recommendations; tune them for your routes, clients, and deployment.
Free tools Windows power users keep installed
One-click scans. No signup required.
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MiB
}
log.Fatal(srv.ListenAndServe())
Add "time" to the imports when using this configuration. The fields do different jobs:
ReadHeaderTimeoutbounds the time allowed to read request headers.ReadTimeoutbounds reading the entire request, including its body. A tight limit can interfere with clients uploading large or slow bodies.WriteTimeoutbounds response writing. Long-running or streaming responses may need a different policy.IdleTimeoutis the wait for the next request on a keep-alive connection.MaxHeaderBytescaps request headers and the request line. It does not cap the request body.
For these timeout fields, zero or negative values have documented no-timeout consequences. Verify the field documentation for the Go version you build with before relying on a particular setting. The Go package documentation shows 10-second read and write timeouts and a 1 MiB header limit as an illustrative configuration; those are documentation examples, not a standard that every service should adopt. See the Go http.Server reference.
Limit request bodies on routes that read them
Header limits do not prevent a client from sending a large body. For a JSON endpoint, wrap r.Body in http.MaxBytesReader before decoding. Choose a limit that matches what the endpoint is designed to accept.
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
)
type payload struct {
Name string `json:"name"`
}
func createHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
const maxBody = 1 << 20 // 1 MiB; choose a limit for this route
r.Body = http.MaxBytesReader(w, r.Body, maxBody)
defer r.Body.Close()
var input payload
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
return
}
if errors.Is(err, io.EOF) {
http.Error(w, "request body is empty", http.StatusBadRequest)
return
}
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte("createdn"))
}
Register it with mux.HandleFunc("/items", createHandler). The decoder reads from the limited body, and an over-limit read produces a *http.MaxBytesError. The example handles that case separately from malformed or empty JSON. For uploads, apply an endpoint-appropriate limit and decide how to report rejected content; do not assume the header setting is a substitute. See http.MaxBytesReader.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Use Go 1.22-aware ServeMux patterns
Route patterns and matching changed significantly in Go 1.22. In Go 1.22 and later, patterns can include an HTTP method and wildcard path segments, for example:
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
w.Write([]byte("item " + id + "n"))
})
Do not copy this pattern into a project targeting an older Go release without checking that release’s syntax and behavior. Wildcards, invalid-pattern handling, and escaped path segments can differ across the compatibility boundary. The package documentation describes the Go 1.22 change and the GODEBUG=httpmuxgo121=1 compatibility setting; that setting is read at process startup, so configure it before launching the program. See the ServeMux pattern documentation and the Go 1.22 release notes when migrating.
Serve HTTPS with certificate material
For TLS, use http.ListenAndServeTLS or the corresponding http.Server method with certificate and key files. For example, the convenience form is:
log.Fatal(http.ListenAndServeTLS(":8443", "server.crt", "server.key", mux))
This requires usable certificate and key material; the standard library does not automatically provision certificates. For local development, plain HTTP on loopback is often simpler. For a service exposed beyond the machine, plan TLS configuration and certificate lifecycle as part of the deployment rather than assuming the listener enables HTTPS by itself. See the TLS listener documentation.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsRank #4
Shut down gracefully and wait for completion
When a process receives an interrupt or termination signal, Server.Shutdown(ctx) closes listeners and idle connections, then waits for active connections to become idle until the context expires. The serving call returns http.ErrServerClosed once shutdown has begun. The main program must wait for the shutdown operation to finish instead of exiting immediately.
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func serve(srv *http.Server) error {
errCh := make(chan error, 1)
go func() {
errCh <- srv.ListenAndServe()
}()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(sigCh)
select {
case err := <-errCh:
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
case <-sigCh:
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
return err
}
err := <-errCh
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!n"))
})
srv := &http.Server{Addr: ":8080", Handler: mux}
if err := serve(srv); err != nil {
log.Fatal(err)
}
}
The 10-second shutdown deadline is an example only. Choose a deadline that fits how long your handlers may legitimately take and the termination window provided by your deployment. If shutdown reaches its deadline, handle the returned error according to your service’s policy; do not report it as a clean, completed drain. Shutdown does not close or wait for hijacked connections such as WebSockets, which require separate coordination. See the shutdown documentation.
Test handlers at the HTTP boundary
The net/http/httptest package lets you verify actual request and response behavior without binding a production port. For a small handler, httptest.NewRecorder is sufficient:
func TestHello(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
recorder := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte("Hello, Go!n"))
})
handler.ServeHTTP(recorder, req)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if got := recorder.Body.String(); got != "Hello, Go!n" {
t.Fatalf("body = %q, want %q", got, "Hello, Go!n")
}
}
Include "net/http/httptest" and "testing" in the test file’s imports. To exercise routing and client-visible behavior through a real test server, use httptest.NewServer(handler), make requests with its client, and close it with defer ts.Close(). If you change test-server configuration, do so before first use. Test status codes, relevant headers, body limits, and error paths—not only the happy-path message. See the official httptest documentation.
Best Value
Troubleshoot common startup and request failures
- “address already in use”: Another process is bound to the chosen address and port. Stop that process or select a port that is available; do not assume that a server failed because of handler code.
- Connection refused: Confirm the program is still running, the address and port match, and the listener bound to an interface reachable from the client. A server bound only to loopback is not reachable from other machines.
- A request hangs or ends unexpectedly: Review the applicable read, write, and idle timeout alongside the handler’s expected duration. A whole-request read deadline can affect slow or large uploads; a write deadline can affect long responses.
- A body is rejected as too large: Check the route’s
MaxBytesReaderlimit and the client payload size. RaisingMaxHeaderByteswill not help because it applies to headers and the request line, not the body. - A ServeMux pattern panics or matches differently after an upgrade: Check the Go version and the Go 1.22 routing compatibility note. Review wildcard and escaped-path behavior before changing patterns in a migration.
- The process exits before requests drain: Ensure the main goroutine waits for
Shutdownand for the serving goroutine’s result. Coordinate upgraded or hijacked connections separately.
Or skip the browser setup
If your Go service needs screenshots of web pages, ScreenshotNeo provides a screenshot API and MCP server. A single GET request can return a PNG, JPEG, WebP, or PDF. Here is the cURL call, with the API key supplied as a placeholder you replace with your own:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request parameters and response details. Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up free for ScreenshotNeo to start with 1,000 screenshots a month and no card.
Frequently Asked Questions
Does Go’s standard library include an HTTP server?
Yes. The net/http package provides handlers, ServeMux routing, HTTP and TLS server functions, and the configurable http.Server type.
Can I use Go 1.22 ServeMux patterns with an older Go version?
Do not assume so. Routing syntax and matching changed in Go 1.22; check the documentation for your target release and its compatibility settings.
Does MaxHeaderBytes limit uploaded files or JSON bodies?
No. It applies to request headers and the request line. Use http.MaxBytesReader to limit a request body.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

