Go chromedp Website Screenshots on an Indian Cloud VPS: Setup and Fixes
Set up Go chromedp screenshots on an India-region Linux VPS, choose viewport, element, or full-page capture, and diagnose browser launch and cancellation errors.
To take a website screenshot with chromedp on an Indian cloud VPS, install Go and a compatible Chrome executable, create a chromedp context with a deadline, navigate to the target URL, wait for the page state you need, and capture the viewport, a DOM element, or the full page. chromedp drives Chrome through the Chrome DevTools Protocol; installing the Go module alone does not install Chrome or its operating-system runtime dependencies. The project documents its installation and headless setup, and its screenshot example demonstrates element and viewport capture.
1. Choose an India-region VPS and verify the browser runtime
Select the VM region based on audience latency, connectivity to the sites you will capture, cost, and any location requirements. AWS lists Mumbai and Hyderabad regions, while Google Cloud lists Mumbai and Delhi locations. Those listings show regional availability, not chromedp performance or suitability for your workload. Compare compute, memory under your expected browser concurrency, storage, outbound transfer, billing, backups, and operational effort. Check current regional terms and prices before provisioning.
AWS Lightsail bundles compute, memory, storage, and transfer; its published Mumbai transfer allowance differs from the listed bundle allowance. Treat plan details as changeable and confirm them on the current AWS Lightsail pricing page. Google Cloud region information is available on its regions and zones page. No universal VM size or minimum RAM can be inferred for arbitrary pages or concurrency.
On the Linux VM, install Go using the official instructions for your chosen distribution, then install and verify a Chrome-compatible executable using that distribution’s official browser guidance. The exact package names and dependencies vary by OS; verify the executable path and version on the VM rather than assuming Chrome is present. The chromedp project also documents its chromedp/headless-shell image as a straightforward headless environment.
go version
command -v google-chrome || command -v chromium || command -v chromium-browser
If you manage Chrome separately, configure its actual path with chromedp.ExecPath. The executable must be compatible with the installed operating-system runtime and the chromedp/CDP code you use.
2. Create a minimal Go screenshot program
Make a directory and initialize a module. This example uses a configurable URL, a selector wait for element capture, an explicit browser executable path option, and a request deadline. The default output is a full-page PNG. It writes the bytes returned by chromedp and reports errors rather than silently saving a partial result.
mkdir chromedp-shot
cd chromedp-shot
go mod init example.com/chromedp-shot
go get github.com/chromedp/chromedp
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"time"
"github.com/chromedp/chromedp"
)
func main() {
targetURL := flag.String("url", "https://example.com", "URL to capture")
output := flag.String("out", "page.png", "Output PNG file")
selector := flag.String("selector", "", "Optional CSS selector to capture as an element")
chromePath := flag.String("chrome", "", "Optional path to Chrome or Chromium")
timeout := flag.Duration("timeout", 60*time.Second, "Overall browser operation deadline")
flag.Parse()
if *targetURL == "" {
log.Fatal("-url must not be empty")
}
// Allocator options configure Chrome launch. Keep the normal defaults unless
// the target VM requires a deliberate, understood change.
opts := append([]chromedp.ExecAllocatorOption{}, chromedp.DefaultExecAllocatorOptions...)
if *chromePath != "" {
opts = append(opts, chromedp.ExecPath(*chromePath))
}
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()
browserCtx, cancelBrowser := chromedp.NewContext(allocCtx)
defer cancelBrowser()
ctx, cancelTimeout := context.WithTimeout(browserCtx, *timeout)
defer cancelTimeout()
tasks := chromedp.Tasks{
chromedp.Navigate(*targetURL),
}
if *selector != "" {
// Wait for the desired element to be visible before capturing it.
tasks = append(tasks, chromedp.WaitVisible(*selector, chromedp.ByQuery))
}
var image []byte
tasks = append(tasks, chromedp.ActionFunc(func(ctx context.Context) error {
if *selector != "" {
return chromedp.Screenshot(*selector, &image, chromedp.ByQuery).Do(ctx)
}
return chromedp.FullScreenshot(&image, 100).Do(ctx)
}))
if err := chromedp.Run(ctx, tasks); err != nil {
log.Fatalf("capture %q: %v", *targetURL, err)
}
if len(image) == 0 {
log.Fatal("capture returned an empty image")
}
if err := os.WriteFile(*output, image, 0o644); err != nil {
log.Fatalf("write %q: %v", *output, err)
}
fmt.Printf("wrote %s (%d bytes)\n", *output, len(image))
}
Save this as main.go. In Go source code, the ampersand expressions in the listing must be written as &image in HTML source, which the browser displays as &image text entity rendering to &image? Use the actual Go operator & in the file. Run it like this:
go run . -url https://example.com -out page.png
go run . -url https://example.com -selector "main" -out main.png
go run . -chrome /usr/bin/chromium -timeout 90s -url https://example.com
In the listing, &image is HTML-escaped source for the Go address-of operator &, rendered as & in this code block context; when copying, ensure the Go file contains the single character &, not the literal entity.
3. Select the capture scope and readiness condition
| Goal | chromedp action | Notes |
|---|---|---|
| Current viewport | chromedp.CaptureScreenshot(&buf) |
Captures the visible browser viewport. Set the viewport dimensions through allocator options such as chromedp.WindowSize(width, height). |
| One DOM element | chromedp.Screenshot(selector, &buf, chromedp.ByQuery) |
Selector must match a visible node; no match returns an error. ScreenshotScale accepts a scale factor. |
| Full page | chromedp.FullScreenshot(&buf, 100) |
Captures beyond the viewport. Quality 100 selects PNG; other documented quality values select JPEG. |
These distinctions are documented in the chromedp package reference. The helper above waits for the selector only when element capture is requested. For a full-page capture, add an application-specific readiness condition where needed:
tasks := chromedp.Tasks{
chromedp.Navigate(targetURL),
chromedp.WaitVisible("main article", chromedp.ByQuery),
chromedp.FullScreenshot(&buf, 100),
}
Navigation completing does not guarantee that a single-page application, lazy-loaded image, chart, or late font has finished rendering. Pick a selector or application condition that signals the content you need. A fixed delay can help when there is no reliable signal, but it adds latency and is not a universal readiness test.
4. Configure Chrome launch options carefully
chromedp’s allocator options include Headless, WindowSize, DisableGPU, and NoSandbox. Headless is the normal server use case. Window size controls viewport dimensions. DisableGPU is available, but should not be treated as a universal repair for launch or rendering failures.
NoSandbox disables Chrome’s sandbox, a security boundary. Do not add it reflexively because a server reports a launch error. First inspect the exact browser error, executable, permissions, runtime, and deployment isolation. Consider disabling the sandbox only when your deployment model justifies the security tradeoff and the consequences are understood. Consult the allocator option definitions for current option behavior.
5. Run it as a service without leaking browser processes
Keep the allocator, browser, and per-request contexts alive for the work they own, then cancel them deliberately. A context deadline bounds a stuck navigation or capture. The Linux chromedp FAQ explains that started Chrome child processes are force-killed to avoid resource leaks, and that losing the browser connection can cancel a chromedp context. A short-lived Go process should not be expected to leave its started browser running for later requests.
For a request-handling service, define whether each request gets a fresh browser or a tab under a longer-lived browser context. Bound concurrent captures, give each operation a deadline, and close request contexts after completion. Monitor process count, memory, and temporary storage under your own workload; the sources do not establish a universal concurrency or memory limit.
6. Troubleshoot launch, navigation, and capture failures
| Symptom | Likely area to inspect | Practical next step |
|---|---|---|
| Chrome executable not found or failed to start | Path, installation, executable permission, or OS runtime dependency | Check command -v, verify the configured ExecPath, and run the executable’s version command as the service user. Read Chrome stderr. |
| Namespace or sandbox launch error | Host/container security policy and sandbox support | Inspect the exact browser diagnostic and VM/container policy. Do not apply NoSandbox without evaluating its security implications. |
context canceled |
Browser connection lost, browser exited or was killed, or parent context was canceled | Check browser stderr, process lifetime, OOM/service-manager events, and the context cancellation chain. Increase a too-short deadline only after identifying the operation that exceeds it. |
| Deadline exceeded or navigation hangs | Slow target, unavailable network path, or page never reaches the expected condition | Log the failing URL and operation, check outbound DNS/connectivity from the VM, and use a bounded, page-specific readiness condition. |
| Element selector returned no node | Selector mismatch, delayed rendering, or content in another frame | Inspect the rendered DOM, wait for a stable visible selector, and confirm the selector addresses the intended document. |
| Screenshot is blank or incomplete | Capture ran before useful content rendered, or the wrong capture scope was selected | Wait for the content, verify the URL and DOM, then choose viewport, element, or full-page capture intentionally. |
| Output file is missing or empty | Write error, empty capture buffer, or wrong working directory | Check the returned error and output path; verify the buffer has bytes before writing. |
| Browser works interactively but fails under a service account | Different PATH, permissions, environment, or writable temporary directory | Reproduce with the same user and environment as the service, and verify access to the executable and required temporary paths. |
These are diagnostic steps, not claims about a particular deployment’s root cause. Capture the complete Go error and browser stderr before changing launch flags. Separate browser startup from navigation, readiness, and image writing to identify which stage fails.
7. Performance, reliability, and cost considerations
- Concurrency: Browser processes and pages consume resources. Start with bounded concurrency and observe CPU, memory, process count, and temporary storage on the target VM under representative pages. No source here establishes an appropriate VM size or throughput figure.
- Readiness: Wait on the specific content needed. An arbitrary long delay increases request time; an insufficient wait produces incomplete output.
- Timeouts: Set an overall deadline and record whether time was spent launching, navigating, waiting, or capturing. Do not retry indefinitely.
- Browser lifecycle: Reuse or restart browsers according to an explicit service design. Context cancellation and process termination are linked; monitor child processes and clean up contexts.
- VPS cost: Include compute, disk, outbound transfer, backups, monitoring, and maintenance in the monthly estimate. Region-specific allowances and prices can change; confirm provider terms before choosing a plan.
- India region: Choose based on your own target audience and target-site network path. Region availability alone is not a latency measurement.
Or skip the browser setup
If the goal is a clean website image rather than operating Chrome, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie and consent banners are accepted and removed before capture, along with known newsletter popups and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify page verdict and billing status. An MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month without a card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month.
Frequently asked questions
Does chromedp install Chrome when I add the Go module?
No. The Go module provides the automation library. Install or provide a compatible browser executable and its OS runtime separately, or use the project’s documented headless-shell environment.
Does choosing Mumbai or Delhi guarantee faster screenshots for Indian visitors?
No. Region inventory does not establish latency for your users or connectivity to each target site. Measure the path that matters to your application.
Should I always disable the Chrome sandbox on a VPS?
No. That disables a browser security boundary. Diagnose the launch environment first and make that change only when the deployment model supports the tradeoff.
Why did my browser process disappear when the Go command ended?
chromedp manages child browser processes to avoid leaks. Design the Go service and browser lifetime together; do not rely on a child started by a finished process remaining available.
Can I capture only a specific component?
Yes. Use chromedp.Screenshot with a CSS selector that matches a visible element, and wait for that element when the page renders asynchronously.


