Skip to main content
Headless mode allows Nova Act to run browsers without a visible UI. This is useful for production environments, CI/CD pipelines, and scenarios where you don’t need to see the browser window.

Enable headless mode

Set headless=True when creating a NovaAct instance:
from nova_act import NovaAct

with NovaAct(
    starting_page="https://example.com",
    headless=True
) as nova:
    nova.act("Search for products")
The browser will run without a visible window, making it ideal for:
  • Automated testing in CI/CD
  • Server environments without displays
  • Background batch processing
  • Resource-constrained systems

Benefits of headless mode

Resource efficient

Lower CPU and memory usage without rendering UI

Faster execution

No rendering overhead means faster workflows

Server friendly

Run on headless servers without X11 or displays

Parallel scaling

Run more sessions in parallel

Remote debugging

When running in headless mode, you can still view and interact with the browser using remote debugging.

Enable remote debugging

Set the browser args environment variable before starting your workflow:
export NOVA_ACT_BROWSER_ARGS="--remote-debugging-port=9222"
Then run your headless workflow:
with NovaAct(
    starting_page="https://example.com",
    headless=True
) as nova:
    nova.act("Complete the task")

Connect to the debugging session

1

Open the debug endpoint

Open a local browser and navigate to:
http://localhost:9222/json
You’ll see a JSON list of all browser pages.
2

Find your page

Look for the item with "type": "page" that matches your workflow.
3

Connect DevTools

Copy the devtoolsFrontendUrl value and paste it into your browser.You’ll now see the Chrome DevTools connected to your headless browser.

Example debug URL

[
  {
    "description": "",
    "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/...",
    "id": "12345678-1234-1234-1234-123456789012",
    "title": "Example Domain",
    "type": "page",
    "url": "https://example.com",
    "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/..."
  }
]

Interactive debugging workflow

You can interact with the headless browser during execution:
import time
from nova_act import NovaAct

with NovaAct(
    starting_page="https://example.com",
    headless=True
) as nova:
    # Navigate to a page
    nova.act("Go to the login page")
    
    # Pause for manual intervention
    print("Connect to http://localhost:9222/json to view/interact")
    time.sleep(60)  # Wait for manual actions
    
    # Continue automation
    nova.act("Complete the checkout process")
This is useful for:
  • Solving CAPTCHAs manually
  • Debugging specific steps
  • Handling edge cases

CAPTCHA handling

Use remote debugging to solve CAPTCHAs in headless workflows:
import time
from nova_act import NovaAct, BOOL_SCHEMA

def solve_captcha_if_present(nova: NovaAct, timeout: int = 300) -> None:
    """Check for CAPTCHA and wait for human to solve it."""
    result = nova.act_get(
        "Is there a CAPTCHA on this page?",
        schema=BOOL_SCHEMA
    )
    
    if result.parsed_response:
        print("CAPTCHA detected!")
        print("Solve it at: http://localhost:9222/json")
        
        # Wait for CAPTCHA to be solved
        start_time = time.time()
        while time.time() - start_time < timeout:
            time.sleep(5)
            
            # Check if CAPTCHA is still there
            check = nova.act_get(
                "Is there still a CAPTCHA on this page?",
                schema=BOOL_SCHEMA
            )
            
            if not check.parsed_response:
                print("CAPTCHA solved!")
                return
        
        raise TimeoutError("CAPTCHA was not solved in time")

with NovaAct(
    starting_page="https://example.com",
    headless=True
) as nova:
    nova.act("Navigate to the protected page")
    solve_captcha_if_present(nova)
    nova.act("Continue with the workflow")

Alert notifications

Send alerts when human intervention is needed:
import boto3
import time
from nova_act import NovaAct, BOOL_SCHEMA

def send_alert(message: str, debug_url: str) -> None:
    """Send SNS notification for manual intervention."""
    sns = boto3.client('sns')
    sns.publish(
        TopicArn='arn:aws:sns:us-east-1:123456789:workflow-alerts',
        Subject='Manual intervention required',
        Message=f"{message}\n\nDebug at: {debug_url}"
    )

with NovaAct(
    starting_page="https://example.com",
    headless=True
) as nova:
    nova.act("Navigate to login")
    
    # Check for CAPTCHA
    result = nova.act_get(
        "Is there a CAPTCHA?",
        schema=BOOL_SCHEMA
    )
    
    if result.parsed_response:
        debug_url = "http://localhost:9222/json"
        send_alert("CAPTCHA needs solving", debug_url)
        
        # Wait for resolution
        time.sleep(300)
    
    nova.act("Complete the task")

Remote host access

If running Nova Act on a remote server, you’ll need to set up port forwarding to access the debug interface.

SSH port forwarding

ssh -L 9222:localhost:9222 user@remote-server
Now you can access http://localhost:9222/json from your local machine, and it will connect to the remote server’s debugging port.

Alternative: Use ngrok

For temporary public access:
# On the remote server
ngrok http 9222
ngrok will provide a public URL you can use to access the debug interface.
Be cautious when exposing debugging ports publicly. Use authentication and firewall rules appropriately.

Production considerations

Performance optimization

with NovaAct(
    starting_page="https://example.com",
    headless=True,
    # Disable unnecessary features
    record_video=False,
    # Use custom logs directory
    logs_directory="/var/log/nova-act"
) as nova:
    nova.act("Complete the task")

Resource limits

Set resource limits in containerized environments:
FROM python:3.10-slim

# Install dependencies
RUN pip install nova-act
RUN playwright install chromium
RUN playwright install-deps chromium

# Set resource limits
ENV NOVA_ACT_BROWSER_ARGS="--disable-dev-shm-usage --no-sandbox"

WORKDIR /app
COPY workflow.py .

CMD ["python", "workflow.py"]
Docker Compose:
services:
  nova-act-worker:
    image: my-nova-act-workflow
    environment:
      - NOVA_ACT_BROWSER_ARGS=--disable-dev-shm-usage --no-sandbox
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G

Debugging options

Customize the remote debugging port:
# Use a custom port
export NOVA_ACT_BROWSER_ARGS="--remote-debugging-port=9223"
Enable additional logging:
# Enable verbose logging
export NOVA_ACT_LOG_LEVEL=10  # DEBUG level
export NOVA_ACT_BROWSER_ARGS="--enable-logging --v=1 --remote-debugging-port=9222"

Troubleshooting

Check that:
  • The browser is running with remote debugging enabled
  • Port 9222 is not blocked by firewall
  • No other process is using port 9222
  • If on remote server, port forwarding is configured
Test with: curl http://localhost:9222/json
Try these fixes:
  • Add --disable-dev-shm-usage to browser args
  • Increase shared memory in Docker: --shm-size=2gb
  • Add --no-sandbox in containerized environments
  • Install required system dependencies
export NOVA_ACT_BROWSER_ARGS="--disable-dev-shm-usage --no-sandbox"
Headless mode should be faster, but check:
  • System resources (CPU, memory)
  • Network latency
  • Video recording is disabled
  • Logs directory has enough space
  • No unnecessary browser extensions
Ensure:
  • Browser version matches DevTools version
  • WebSocket connection is working
  • No proxy interfering with localhost
  • Browser hasn’t crashed or restarted

Best practices

Development vs production

Use headed mode for development:
with NovaAct(
    starting_page="https://example.com",
    headless=False  # See what's happening
) as nova:
    nova.act("Test the workflow")

Monitoring

Log headless session activity:
import logging

logger = logging.getLogger(__name__)

with NovaAct(
    starting_page="https://example.com",
    headless=True,
    logs_directory="/var/log/nova-act"
) as nova:
    logger.info("Starting headless workflow")
    
    try:
        result = nova.act("Complete the task")
        logger.info(f"Workflow completed: {result.metadata}")
    except Exception as e:
        logger.error(f"Workflow failed: {e}")
        raise

Next steps

Logging & traces

View traces and debug workflows

Proxy configuration

Configure proxy servers

S3 storage

Store session data in S3

Error handling

Handle errors in production

Build docs developers (and LLMs) love