Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/binary-person/rammerhead/llms.txt

Use this file to discover all available pages before exploring further.

Most Rammerhead problems fall into a small set of categories: JavaScript rewriting failures, session or storage misconfiguration, network-level issues with WebSockets or HTTP/2, and reverse proxy setup mistakes. Use the items below to identify the root cause and apply a targeted fix.
Blank pages usually mean the JavaScript rewriting pipeline is failing or producing output that the browser rejects.Things to try:
  1. Open the browser console on the proxied page and look for JavaScript errors. An error referencing hammerhead or __get$ points to a rewriting failure.
  2. Disable URL shuffling for the session to rule it out as a source of confusion:
    curl "http://localhost:8080/editsession?id=SESSION_ID&enableShuffling=0"
    
  3. Enable debug logging to see the full request flow:
    const logger = new RammerheadLogging({ logLevel: 'debug' });
    
  4. Set the DEVELOPMENT environment variable before starting the server. This causes the proxy to serve unminified rammerhead.js and hammerhead.js, making stack traces readable:
    DEVELOPMENT=1 node src/server.js
    
  5. Check that crossDomainPort is configured and reachable. Without a functioning cross-domain port, sites that issue cross-origin subrequests will silently fail.
Google login is a known limitation of Rammerhead. Google’s login flow uses advanced browser fingerprinting and security checks that detect the proxy environment and block authentication. This is not a configuration problem — it is a fundamental constraint of how Google’s authentication works.If your use case requires Google authentication, consider authenticating users directly on the origin site and exporting the resulting cookies into the session via session.externalProxySettings or a cookie injection mechanism.
Memory growth typically comes from one of two sources: the JS rewrite cache filling up in memory, or sessions accumulating in the in-memory cache.Tune the disk JS cache size or switch to memory caching:The default configuration uses RammerheadJSFileCache (disk-backed, 5 GB limit) in src/config.js. If disk I/O is slow, switch to the in-memory cache with a size limit that fits your available RAM:
const { RammerheadProxy, RammerheadJSMemCache } = require('rammerhead');

const proxy = new RammerheadProxy({
  // 200 MB in-memory LRU cache
  jsCache: new RammerheadJSMemCache(200 * 1024 * 1024)
});
Alternatively, keep disk caching but reduce the max size or item count to limit disk usage:
const path = require('path');
const { RammerheadProxy, RammerheadJSFileCache } = require('rammerhead');

const proxy = new RammerheadProxy({
  jsCache: new RammerheadJSFileCache(
    path.join(__dirname, 'cache-js'),
    1 * 1024 * 1024 * 1024, // 1 GB instead of 5 GB
    10000,                   // max 10 000 cached entries
    false                    // set true when using multiple workers
  )
});
Tune the session cache timeout:Reduce cacheTimeout so inactive sessions are flushed to disk sooner:
const sessionStore = new RammerheadSessionFileCache({
  saveDirectory: './sessions',
  cacheTimeout: 1000 * 60 * 5,      // flush after 5 minutes of inactivity
  cacheCheckInterval: 1000 * 60 * 2  // check every 2 minutes
});
Enable deleteUnused:Sessions created but never used (e.g., from bots probing /newsession) are kept in memory until cacheTimeout expires. With deleteUnused: true (the default), these are discarded without being written to disk.
Connection reset errors are expected in a proxy environment — remote servers close connections abruptly for many legitimate reasons. Rammerhead handles these automatically via addMoreErrorGuards, which is loaded at startup inside RammerheadProxy.The error guard catches ECONNRESET, EPIPE, ETIMEDOUT, ERR_INVALID_PROTOCOL, and similar codes and prevents them from crashing the worker. A process.on('uncaughtException', ...) handler acts as a last-resort failsafe for any that slip through.If you are seeing unhandled crashes (not just console noise), check:
  • Whether the error message includes one of the patterns above. If it does, it should have been swallowed — check your Node.js version for known issues.
  • Whether you are throwing inside a pipeline handler without a try/catch. Uncaught errors in async pipeline handlers propagate to the event loop.
  • The ws package version. Rammerhead requires ws@^8.2.0. A mismatched version can surface unusual WebSocket errors.
Sessions are serialized to <saveDirectory>/<id>.rhfsession files. If sessions disappear on restart, check:
  1. Permissions — the process user must have read and write access to saveDirectory.
    ls -la ./sessions
    
  2. deleteCorruptedSessions — if Node.js crashed during a write, the session file may be truncated JSON. With deleteCorruptedSessions: true, these are silently removed on next read. Set it to false temporarily to see parse errors in the log.
  3. deleteUnused — sessions that were created but never used are not written to disk even once. They are only held in memory until cacheTimeout expires, then discarded. This is intentional to avoid filling disk with bot-created sessions.
  4. Stale cleanup — verify that staleTimeout and maxToLive are not set too aggressively for your use case.
Rammerhead starts two HTTP servers: one on port and one on crossDomainPort. Hammerhead uses the second server to simulate cross-origin requests so that sites using cross-origin iframes work correctly.If cross-domain iframes break:
  • Make sure crossDomainPort is not null in your RammerheadProxy options. Setting it to null merges both servers onto one port, which disables cross-domain simulation.
  • Make sure crossDomainPort is exposed at the network level (firewall, reverse proxy, Docker port mapping).
  • In a reverse proxy setup, ensure getServerInfo returns the correct crossDomainPort as seen by the client:
    getServerInfo: (req) => ({
      hostname: 'proxy.example.com',
      port: 443,
      crossDomainPort: 8443,
      protocol: 'https:'
    })
    
When Rammerhead sits behind nginx, Caddy, or another reverse proxy, the getServerInfo function must return the external hostname, port, and protocol — not the internal bind address. Without this, hammerhead rewrites URLs using the internal address, which the browser cannot reach.
// Hard-coded external address
getServerInfo: () => ({
  hostname: 'proxy.example.com',
  port: 443,
  crossDomainPort: 8443,
  protocol: 'https:'
})

// Dynamic — read from the Host header
getServerInfo: (req) => {
  const { hostname } = new URL('http://' + req.headers.host);
  return {
    hostname,
    port: 443,
    crossDomainPort: 8443,
    protocol: 'https:'
  };
}
The default getServerInfo reads hostname and port from the Host request header and infers protocol from req.socket.encrypted. This works when the proxy listens directly on the public internet, but produces incorrect output behind a TLS-terminating reverse proxy (the socket will appear unencrypted).
WebSocket problems usually fall into two categories: upgrade requests being rejected, and connected sockets disconnecting unexpectedly.Upgrade rejections:
  • Confirm that your reverse proxy forwards the Upgrade, Connection, and Sec-WebSocket-* headers. nginx requires explicit proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade" directives.
  • Check that the ws package version is ^8.2.0 as specified in package.json. Run npm ls ws to see what is installed.
Unexpected disconnections:
  • These are typically ECONNRESET or EPIPE errors from the remote server. They are caught by addMoreErrorGuards and should not crash the server.
  • As of v1.2.64, WebSocket errors are explicitly caught at the ws server level. Make sure you are on the latest version.
HTTP/2 and WebSocket:
  • Some sites (such as web.whatsapp.com) require an HTTP/2 connection before they accept a WebSocket upgrade. HTTP/2 is enabled by default since v1.2.64. If you disabled it (disableHttp2: true), re-enable it for better compatibility.
Set logLevel to 'debug' or 'traffic' to get detailed per-request output.Via the logger constructor:
const { RammerheadLogging } = require('rammerhead');

const logger = new RammerheadLogging({ logLevel: 'debug' });
Via the DEVELOPMENT environment variable:The built-in src/config.js switches to 'debug' log level automatically when DEVELOPMENT is set:
DEVELOPMENT=1 node src/server.js
Setting DEVELOPMENT=1 also serves unminified client-side scripts (rammerhead.js instead of rammerhead.min.js), which produces more readable stack traces in the browser.Log levels (from least to most verbose):
LevelWhat it shows
disabledNothing
errorErrors only
warnErrors and warnings
infoNormal startup and lifecycle events
trafficEvery proxied request URL and IP
debugFull internals including session cache operations
You can change the level at runtime without restarting:
logger.logLevel = 'traffic';

Build docs developers (and LLMs) love