June 17, 2026
How to Build a Selenium Grid on AWS
Learn how to design and deploy a Selenium Grid on AWS with EC2 browser nodes, hub-node architecture, scaling, security, and CI integration. Includes tradeoffs and simpler alternatives.
Running browser tests on a laptop is fine until you need parallelism, repeatability, and a clean separation between test execution and the developer machine. That is usually the point where teams start looking at a Selenium Grid on AWS, because EC2 gives you control over instance sizing, operating system images, browser versions, networking, and cost.
A grid on AWS can be a solid choice when you need to run tests against real browsers at scale, but it is not a free lunch. You are also signing up for node lifecycle management, browser updates, log collection, session routing, and a fair amount of operational work. If your team wants cloud browser execution without maintaining EC2 browser nodes, a managed platform like Endtest is often the simpler path, especially when the main goal is reliable browser coverage rather than building infrastructure.
This tutorial walks through how to design and deploy an AWS Selenium Grid using EC2 instances and browser nodes, what the common failure modes look like, and how to make the setup maintainable enough for real teams.
What a Selenium Grid on AWS is actually solving
At a high level, Selenium Grid lets your test runner send browser sessions to remote machines instead of launching browsers locally. The classic Selenium model uses a central hub and multiple nodes. The hub receives new session requests, then forwards each session to a node that can satisfy the requested browser and platform combination. Selenium’s current Grid architecture is documented in the official Selenium Grid docs.
On AWS, that usually means:
- one EC2 instance for the Grid controller, or a small set of controller services,
- several EC2 instances acting as browser nodes,
- a network setup that allows test runners and nodes to communicate securely,
- optional autoscaling or ephemeral node replacement.
This design is useful when you need:
- multiple browser versions,
- isolated test runs,
- parallel execution,
- predictable machine images,
- access to browser-level debugging artifacts.
It is less useful when your team mainly wants fewer maintenance responsibilities. In that case, cloud browser execution without infrastructure ownership may be a better fit.
A practical architecture for AWS Selenium Grid
You have a few deployment patterns, but the simplest one is also the easiest to reason about.
Option 1, one controller plus static EC2 nodes
This is the most straightforward approach:
- an EC2 instance runs the Grid controller,
- each node is another EC2 instance with Chrome, Firefox, or Edge installed,
- the test suite connects to the controller using the remote WebDriver endpoint.
Pros:
- simple to understand,
- easy to debug,
- no container orchestration required,
- good for small to medium teams.
Cons:
- manual node management,
- slower scaling,
- more patching and image maintenance.
Option 2, controller on EC2, nodes launched from baked AMIs
This is usually the best balance for AWS Selenium Grid EC2 deployments. You build a golden Amazon Machine Image, or AMI, with:
- operating system hardening,
- browser binaries,
- matching driver versions,
- node startup scripts,
- log shipping agents.
When demand increases, you launch more instances from that AMI.
Pros:
- reproducible nodes,
- faster provisioning,
- fewer snowflake servers,
- easier rollback when a browser update breaks tests.
Cons:
- more setup work up front,
- AMI lifecycle needs discipline.
Option 3, containerized Grid on EC2 or ECS
You can also run Grid components in Docker, but if your goal is browser testing AWS on real machines, containerization only solves part of the problem. Browser behavior can differ between containers and full desktop-like environments, depending on your test surface, fonts, GPU assumptions, downloads, and OS integrations.
For teams that are already deep in infrastructure work, containers can help with packaging and reproducibility. For teams trying to reduce flaky UI tests, the simpler problem is often better solved by standardizing full EC2 images first.
If the main pain is instability, get the browser, driver, and OS image under control before introducing more abstraction.
Choosing the right EC2 instance types
There is no single instance family that fits every browser workload. Your choice depends on browser count, test complexity, and whether tests are CPU-bound, memory-bound, or I/O-bound.
A few practical guidelines:
- start with general-purpose instances for small grids,
- pick more memory if browsers crash under load,
- avoid oversubscribing a node with too many concurrent sessions,
- benchmark with your real test suite, not a synthetic hello-world test.
A common mistake is assuming that because browser automation seems light, any small instance will work. A modern browser plus automation framework plus app under test can consume much more memory than expected, especially when the page loads large bundles, uses canvas, or keeps many tabs open.
A sensible first baseline is:
- one EC2 node per browser session for heavy tests,
- maybe two sessions per node for lighter smoke tests,
- separate node pools for Chrome, Firefox, and Edge if your suite needs cross-browser coverage.
Installing Selenium Grid on the controller
Selenium Grid can be run as standalone or in distributed mode. For many teams, standalone is enough for a small proof of concept. For larger setups, the distributed architecture gives more control over components.
A minimal controller setup on Linux might look like this:
bash wget https://github.com/SeleniumHQ/selenium/releases/download/4.24.0/selenium-server-4.24.0.jar java -jar selenium-server-4.24.0.jar hub
In real usage, you will likely want a systemd service so the controller starts on boot and restarts after failures.
Example service file:
ini [Unit] Description=Selenium Grid Hub After=network.target
[Service] User=ubuntu WorkingDirectory=/opt/selenium ExecStart=/usr/bin/java -jar /opt/selenium/selenium-server.jar hub Restart=always
[Install] WantedBy=multi-user.target
Do not expose the controller directly to the public internet unless you have a very strong reason. Put it behind a security group with limited ingress, or even better, keep it private within a VPC and let CI runners or bastion-hosted jobs talk to it.
Configuring EC2 browser nodes
Nodes are where most of the pain lives. The basic requirements are straightforward:
- a compatible browser installed,
- a matching WebDriver or a Selenium version that uses Selenium Manager effectively,
- node registration with the controller,
- enough memory and CPU for stable sessions,
- visibility into logs and browser output.
A node startup command might look like this:
bash
java -jar selenium-server-4.24.0.jar node
–hub http://grid-controller:4444
–max-sessions 1
For a more production-oriented deployment, you will want node config files, not just ad hoc commands. Selenium supports capability configuration so nodes advertise what they can run. That matters when you mix browsers, operating systems, and session limits.
Example node config:
{ “browserName”: “chrome”, “platformName”: “linux”, “maxSessions”: 1, “seleniumManager”: true }
Keep the concurrency conservative at first. Browser tests often become flaky when too many sessions share one machine, because contention shows up as timeouts, rendering lag, and resource starvation. One browser per node is usually easier to reason about than a heavily packed node.
Building a golden AMI for browser nodes
If you do only one infrastructure thing well, make it the AMI pipeline. A reproducible node image prevents a lot of weirdness.
Your image build should pin or standardize:
- OS version,
- browser versions,
- fonts and system packages,
- Java runtime if needed,
- node service definitions,
- logs and temporary directories.
A typical flow is:
- create a base EC2 instance,
- install browser dependencies,
- install your chosen browser versions,
- copy in the Selenium node binaries and config,
- verify the node can register to the controller,
- capture the AMI,
- launch all future nodes from that image.
If browser tests start failing after a seemingly harmless system patch, you want the ability to roll back to the previous AMI quickly. This is one of the best reasons to prefer immutable browser nodes over pets.
Networking, security groups, and DNS
Browser grids often fail because of network assumptions, not browser problems.
Make sure you plan for:
- inbound access to the controller from your CI network,
- node-to-controller communication,
- outbound internet access if tests need external dependencies,
- DNS resolution inside the VPC,
- TLS if the grid is accessed across trust boundaries.
At a minimum, restrict access with security groups. If your CI runners live in the same VPC, keep the grid private. If they are external, consider a VPN, private link-like patterns, or a tightly controlled ingress point.
If you are using remote WebDriver URLs, confirm that the DNS name is stable. Moving the controller behind a load balancer can help with resilience, but it also adds another layer to debug when sessions fail to establish.
A Selenium test that targets the AWS grid
The client-side code is usually simple. The hard part is making sure your remote endpoint, capabilities, and session assumptions are correct.
Here is a compact Python example using Selenium Remote WebDriver:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options() options.set_capability(“browserName”, “chrome”)
driver = webdriver.Remote( command_executor=”http://grid-controller.internal:4444/wd/hub”, options=options, )
driver.get(“https://example.com”) print(driver.title) driver.quit()
The exact endpoint path depends on your Grid version and configuration, so verify it against the official Selenium documentation.
If your tests are flaky, do not immediately blame Selenium Grid. Check these first:
- is the node out of memory,
- are you waiting for the right condition,
- did the browser version change,
- are your locators stable,
- did the app under test become slower,
- is the CI runner timing out before the browser completes.
Making the grid resilient instead of fragile
A grid can turn into a single point of failure if you treat it like a pet server. Better practices include:
Make nodes disposable
When a node becomes unhealthy, terminate and replace it. Do not keep repairing a broken browser VM by hand unless you are investigating a one-off issue.
Ship logs off the node
At a minimum, capture:
- Selenium server logs,
- browser logs,
- system logs,
- test runner logs,
- screenshots and page source on failure.
Without logs, every failure turns into guesswork. CloudWatch Logs is a natural choice on AWS, but any centralized log system is better than SSHing into instances after a failed run.
Keep the browser count per node low
Parallelism is good, but only if it does not make the environment unstable. If you need more parallel capacity, add more nodes rather than increasing the browser count per machine too aggressively.
Pin versions deliberately
A browser update can break locators, rendering, downloads, or authentication flows. Pin the image, test updates in staging, then promote the new AMI after validation.
Separate smoke and regression pools
Not every test deserves the same infrastructure. Smoke tests may use a smaller, faster pool. Full regression suites may use more isolated nodes with longer timeouts and stronger observability.
Integrating with CI/CD
Most teams run grid-based tests from a pipeline. Here is a simple GitHub Actions example that points tests at a remote grid.
name: ui-tests
on: push: pull_request:
jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: “3.11” - run: pip install -r requirements.txt - run: pytest tests/ui env: SELENIUM_REMOTE_URL: http://grid-controller.internal:4444/wd/hub
For CI reliability, prefer:
- explicit retries only for known transient failures,
- sensible test grouping,
- pipeline timeouts that leave room for browser setup,
- artifacts on failure,
- tagging tests by browser compatibility or smoke vs regression.
Be careful with retries. They can hide real instability. If a test only passes on the second or third attempt, it is telling you something about timing, data, or infrastructure.
Cost and operational tradeoffs
AWS gives you control, but the bill includes more than EC2 hourly charges.
You should account for:
- EC2 instances for controller and nodes,
- EBS volumes and snapshots,
- data transfer,
- log storage,
- engineering time to maintain the image pipeline,
- time spent debugging browser and driver mismatches.
If your team already has strong AWS operational maturity, these costs may be fine. If the test suite is small or the organization does not want another infrastructure surface area, the management overhead can outweigh the flexibility.
That is where a managed browser automation platform becomes attractive. Endtest, for example, offers cloud execution with an agentic AI Test automation workflow, so teams can create and maintain tests without running their own EC2 browser nodes or stitching together the surrounding infrastructure. For teams evaluating whether to build or buy, the Endtest vs Selenium comparison is a useful place to sanity-check the tradeoffs.
When AWS Selenium Grid is the right choice
An AWS Selenium Grid makes sense when you need:
- direct control over OS and browser versions,
- private networking and VPC isolation,
- custom debugging and logging pipelines,
- a self-managed approach that fits an existing platform team,
- the ability to tune instance types and capacity very precisely.
It is usually a poor fit when:
- the team wants to focus on product testing, not infrastructure,
- browser maintenance is already a source of flaky behavior,
- test volumes are modest,
- there is no appetite for AMI management and node health automation.
A useful rule of thumb is this: if your organization is already comfortable operating ephemeral compute and image pipelines, a grid on EC2 can be a good foundation. If the grid itself is becoming a project, you may be better served by a simpler cloud browser testing platform.
A decision checklist before you build
Before you launch the first node, answer these questions:
- How many parallel sessions do we actually need?
- Which browsers and versions are required?
- Do we need Linux, Windows, or macOS coverage?
- Will test traffic stay inside a private network?
- Who owns AMI updates and rollback?
- Where do logs, screenshots, and videos go?
- How will we detect unhealthy nodes?
- What is the process for browser upgrades?
- What is the fallback if the grid is down?
If those answers are fuzzy, the build will likely feel easier than the maintenance phase. That is usually where the hidden cost lives.
Final thoughts
Building a Selenium Grid on AWS is not difficult in the abstract, but making it dependable takes real operational discipline. The core pieces are straightforward, a controller, EC2 browser nodes, a stable AMI, network controls, and CI integration. The challenge is keeping the environment boring enough that your team can trust it.
If you need granular control and already run infrastructure like this well, AWS Selenium Grid can be a strong choice. If you mainly want dependable browser execution without owning the browser fleet, a managed alternative such as Endtest can save a lot of time, especially for teams migrating existing Selenium suites into a platform-native workflow.
Either way, the goal is the same, fewer flaky tests, clearer failures, and browser coverage that supports release confidence instead of slowing the team down.