2025, Dec 29 07:00

Why Selenium Firefox Headless Hangs on Remote Servers and How Switching from Snap to Deb Fixes the Timeout

Fix Selenium Firefox headless hangs on servers: diagnose ReadTimeoutError, absent geckodriver logs, and replace snap Firefox with the deb build reliably

Moving a stable Selenium workflow from a local machine to a remote server can expose subtle environment differences. A common symptom is a hang when creating a Firefox WebDriver in headless mode, followed by a timeout. Below is a minimal case that behaved correctly on a developer laptop, but stalled on a server until the root cause in the browser installation was fixed.

Repro in code

# necessary imports
from time import sleep
from selenium.webdriver.firefox.service import Service
from seleniumwire import webdriver as sw_driver
# end imports
def init_ff_wire_with_proxy(proxy_host=None, proxy_port=None):
    print("Point 1")
    fx_opts = sw_driver.FirefoxOptions()
    print("Point 2")
    fx_opts.add_argument("--headless")
    print("Point 3")
    fx_opts.binary_location = '/usr/bin/firefox'
    print("Point 4")
    exec_path = '/root/geckodriver/geckodriver'
    print("Point 5")
    svc = Service(exec_path)
    print("Point 6")
    svc.log_path = '/root/[my folder]/geckodriver.log'
    print("Point 7")
    if not proxy_host or not proxy_port:
        print("Point 8")
        return sw_driver.Firefox(
            options=fx_opts,
            service=svc
        )
    wire_conf = {
        'proxy': {
            'http': f'socks5://{proxy_host}:{proxy_port}',
            'https': f'socks5://{proxy_host}:{proxy_port}',
            'no_proxy': 'localhost,127.0.0.1',
            'proxy_type': 'manual'
        }
    }
    return sw_driver.Firefox(
        service=svc,
        options=fx_opts,
        seleniumwire_options=wire_conf
    )
def probe_gecko():
    browser = init_ff_wire_with_proxy()
    print("Point 9")
    browser.get([some site])
    print("Point 10")
    sleep(3)
    print("Point 11")
    browser.quit()

On the server, the execution reached the constructor call and then stalled:

return sw_driver.Firefox(
    options=fx_opts,
    service=svc
)

After a long wait it failed with:

urllib3.exceptions.ReadTimeoutError: HTTPConnectionPool(host='localhost', port=54001): Read timed out. (read timeout=120)

The geckodriver log file specified in svc.log_path was not created. Selenium version was 4.31.0, and geckodriver started independently and listened on port 4444.

What’s actually going on

The symptoms point to the WebDriver server waiting for the browser to come up and connect back, but never seeing that happen, hence the timeout. In this case the underlying cause was the Firefox build installed on the server. The browser had been installed via snap, and that particular packaging choice prevented the session from starting correctly in headless mode on that host.

Fix

The resolution was straightforward: remove the snap version of Firefox and install the deb build. After switching the browser installation, the exact same Python code initialized the session and ran without timeouts, headless included.

If you want to sanity-check the browser on a headless server before touching Selenium, you can try launching it directly and capturing stderr/stdout, for example: firefox --headless > errors.txt 2>&1. Also note that currently Selenium can download a driver automatically if it can’t find one, which may simplify your setup.

Working code after the environment fix

No code changes were required; the corrected environment was enough. For completeness, here’s the same driver bootstrap with the names shown above:

from time import sleep
from selenium.webdriver.firefox.service import Service
from seleniumwire import webdriver as sw_driver
def init_ff_wire_with_proxy(proxy_host=None, proxy_port=None):
    print("Point 1")
    fx_opts = sw_driver.FirefoxOptions()
    print("Point 2")
    fx_opts.add_argument("--headless")
    print("Point 3")
    fx_opts.binary_location = '/usr/bin/firefox'
    print("Point 4")
    exec_path = '/root/geckodriver/geckodriver'
    print("Point 5")
    svc = Service(exec_path)
    print("Point 6")
    svc.log_path = '/root/[my folder]/geckodriver.log'
    print("Point 7")
    if not proxy_host or not proxy_port:
        print("Point 8")
        return sw_driver.Firefox(
            options=fx_opts,
            service=svc
        )
    wire_conf = {
        'proxy': {
            'http': f'socks5://{proxy_host}:{proxy_port}',
            'https': f'socks5://{proxy_host}:{proxy_port}',
            'no_proxy': 'localhost,127.0.0.1',
            'proxy_type': 'manual'
        }
    }
    return sw_driver.Firefox(
        service=svc,
        options=fx_opts,
        seleniumwire_options=wire_conf
    )
def probe_gecko():
    browser = init_ff_wire_with_proxy()
    print("Point 9")
    browser.get([some site])
    print("Point 10")
    sleep(3)
    print("Point 11")
    browser.quit()

Why this matters

When automating browsers in CI or on remote servers, it’s easy to blame Selenium, proxies, or driver flags for hangs and timeouts. In reality, the packaging and provenance of the browser binary itself can be the deciding factor. Knowing that the browser build can influence headless startup behavior saves time and directs you to verify the runtime first.

Takeaways

If you encounter a stall at WebDriver construction with a subsequent read timeout and no geckodriver logs, validate the browser installation on the host. In the case above, replacing the snap-installed Firefox with the deb package resolved the issue immediately. A quick manual run with --headless helps confirm the environment, and you can rely on Selenium’s ability to fetch drivers automatically if that fits your setup. Ensuring the right browser build is often the simplest path to a stable headless session.