How to Self-Host Nitter with Docker
Self-hosting Nitter with Docker used to be one of the cleanest weekend projects a developer could take on: a single container, a configuration file, and a private, ad-free Twitter reader running on your own server. The mechanics of the Docker setup are still straightforward. What has changed dramatically is whether the result actually works, and this guide is honest about both.
This walkthrough covers the deployment itself and, just as importantly, the current reality of running Nitter after X removed guest-token access in January 2024. By the end, you will understand the exact components involved, how to configure and launch them, how to diagnose the failures that now dominate self-hosted Nitter, and whether the effort is worth it for your situation.
Read this before you start
The original Nitter relied on anonymous guest tokens to read Twitter’s internal endpoints, the mechanism the companion article explains in detail. X disabled that access in early 2024, which is why nearly every public Nitter instance went offline at once. The Docker image still builds and runs, but a stock configuration will return errors instead of tweets.
Modern community forks work around this by authenticating with real X account credentials rather than guest tokens. This changes the risk profile completely:
- The accounts you use are frequently rate-limited, suspended, or banned.
- You should only use throwaway accounts you are prepared to lose.
- Instances break whenever X alters its internal endpoints.
- Maintenance is ongoing, not one-time.
If you accept those trade-offs, the Docker deployment below is the least painful way to run an instance. If you do not, skip to the closing section on more durable alternatives.
It is worth stating the legal and policy dimension plainly as well. Accessing X’s internal endpoints with automated account sessions violates the platform’s terms of service. That is precisely why the accounts get suspended. Self-hosting Nitter is a technical exercise in resilience against a moving target, not a supported integration. Treat everything below as experimentation on infrastructure you own, with credentials you can afford to lose.
Prerequisites and server sizing
Nitter is a compiled Nim application with a small runtime footprint, and Redis is efficient. The workload is therefore modest for personal use, and the sizing question is driven more by how many concurrent readers and feeds you serve than by raw compute.
For a single-user or small-group instance, the following is comfortable:
- 1 vCPU.
- 1 GB of RAM, of which Redis will use a few hundred megabytes at most under light load.
- 10 GB of disk, the majority of which is the container images and logs rather than data.
- A modern Linux distribution with a current kernel.
For an instance serving heavier RSS polling or several dozen concurrent users, move to 2 vCPUs and 2 GB of RAM, and keep an eye on the Redis memory footprint as the cache grows. The dominant constraint in practice is not your server. It is the rate limit and ban behavior of the X accounts in your session pool, which no amount of hardware will fix.
You will also need:
- Docker Engine and the Docker Compose plugin installed and running. Verify with
docker --versionanddocker compose version. - A non-root user added to the
dockergroup, so you are not running everything as root. - A domain or subdomain with DNS you control, if you intend to expose the instance over HTTPS.
- One or more X accounts you are willing to sacrifice for token generation.
What you need at a glance
- A Linux server or VPS with Docker and Docker Compose installed.
- A domain or subdomain if you want HTTPS and public access.
- One or more X accounts you are willing to sacrifice for token generation.
- A Redis instance for caching, which the compose file provisions for you.
Step 1: Create the project directory
Create a working directory and the two files Nitter needs, a configuration file and a Docker Compose definition.
mkdir nitter && cd nitter
touch nitter.conf docker-compose.yml
Keeping everything in one directory makes the instance trivial to back up, move, or tear down. The entire state of a self-hosted Nitter is these two files plus the Redis volume, so a single directory and a single named volume is all you need to reason about.
Step 2: Write the configuration
Nitter reads settings from nitter.conf. A minimal configuration defines the server host and port, the Redis connection, and basic preferences.
[Server]
hostname = "nitter.example.com"
title = "nitter"
address = "0.0.0.0"
port = 8080
https = false
httpMaxConnections = 100
[Cache]
listMinutes = 240
rssMinutes = 10
redisHost = "nitter-redis"
redisPort = 6379
[Config]
hmacKey = "replace-with-a-random-secret"
base64Media = false
enableRSS = true
[Preferences]
theme = "Nitter"
replaceTwitter = "nitter.example.com"
Generate a real random value for hmacKey. Never ship the placeholder.
Understanding each configuration key
The configuration file is short, but each key has a real effect on behavior and performance. The following reference explains what each one does so you can tune the instance deliberately rather than by trial and error.
hostnameis the public hostname of your instance. It is used to build absolute links, RSS feed URLs, and media references. Set it to the domain your readers will actually use, notlocalhost, or generated links will be wrong.titleis the display name shown in the interface. It is cosmetic.addressis the interface Nitter binds to inside the container.0.0.0.0means all interfaces, which is correct when Docker is mapping the port outward.portis the port Nitter listens on inside the container. It must match the container side of the port mapping in your Compose file.httpstells Nitter whether it is being served over HTTPS. Leave itfalsewhen a reverse proxy terminates TLS in front of the container, because the proxy handles the secure connection and Nitter still speaks plain HTTP internally.httpMaxConnectionscaps the number of outbound connections Nitter opens to the upstream. Raising it does not raise your effective throughput once account rate limits are the bottleneck, so leave it at a sane default.listMinutescontrols how long timeline and profile responses are cached in Redis. A higher value reduces upstream requests, which is desirable now that every request costs account budget. It also increases staleness. Four hours is a reasonable balance for low-traffic personal use.rssMinutescontrols how long RSS feed responses are cached. RSS readers poll frequently, so this value directly affects how much upstream pressure your feeds generate. Do not set it too low, or aggressive readers will burn through your rate limits.redisHostandredisPortpoint Nitter at the Redis service. When both run in the same Compose stack,redisHostis the service name,nitter-redisin the examples here, not an IP address.hmacKeyis a secret used to sign media and other tokens. It must be a genuine random string and must be kept private. Anyone who has it can forge signed URLs.base64Mediacontrols whether media is proxied inline. Leaving itfalsekeeps pages lighter.enableRSStoggles the RSS feature. For most people self-hosting Nitter, RSS is the entire point, so keep it enabled.themeselects the visual theme. It is cosmetic.replaceTwitterrewrites outbound Twitter links to point back at your instance, so navigation stays inside your private reader.
Two rules matter most here. First, hostname and replaceTwitter should reflect the real public address, or links break. Second, the cache durations are now a rate-limit management tool, not just a performance knob, because every cache miss spends account budget you cannot easily replenish.
Step 3: Define the Docker Compose stack
The stack runs two services: Nitter itself and a Redis cache.
services:
nitter:
image: zedeus/nitter:latest
container_name: nitter
ports:
- "8080:8080"
volumes:
- ./nitter.conf:/src/nitter.conf:ro
depends_on:
- nitter-redis
restart: unless-stopped
nitter-redis:
image: redis:7-alpine
container_name: nitter-redis
command: redis-server --save 60 1 --loglevel warning
volumes:
- nitter-redis:/data
restart: unless-stopped
volumes:
nitter-redis:
A few details in this file are deliberate and worth understanding.
The nitter.conf file is mounted read-only with the :ro suffix, which prevents the container from modifying your configuration and makes the container’s behavior fully determined by the file you control on the host.
The depends_on directive ensures Redis starts before Nitter, but note that it only waits for the container to start, not for Redis to be ready to accept connections. In practice Redis comes up fast enough that this is rarely a problem, and restart: unless-stopped covers the rare race by restarting Nitter if it fails to connect on first launch.
The Redis --save 60 1 argument tells Redis to persist to disk if at least one key changed in the last sixty seconds. For a cache this is optional, because the data can always be regenerated, but persisting it across restarts avoids a cold cache and therefore a burst of upstream requests every time you restart the stack.
The named nitter-redis volume holds the cache data. It is the only stateful part of the deployment besides your configuration.
If you intend to pin a known-good build rather than track latest, replace the image tag with a specific version. Tracking latest means an image update can change parsing behavior without warning, which is a double-edged sword given how often upstream changes force new builds.
Step 4: Provide account sessions
This is the step that stock guides from before 2024 do not mention, and the step that determines whether your instance works at all. Current forks require a session file containing authenticated account tokens rather than guest tokens.
The practical process is:
- Use a maintained Nitter fork that supports account-based sessions.
- Run its session-generation script with credentials from a throwaway X account.
- Mount the resulting session file into the container.
- Add more accounts to the pool if you hit rate limits.
Because the exact tooling changes as forks evolve, follow the session instructions in the specific fork’s current README rather than any fixed command here. Treat every account in the pool as disposable.
Managing the account-session pool
The session pool is the heart of a modern Nitter instance and the source of nearly all of its instability. Understanding how it behaves saves a great deal of confusion later.
Each account you add contributes its own rate-limit budget. Nitter distributes requests across the pool, so more accounts means more total capacity and more resilience when individual accounts are throttled. A single-account instance will hit limits quickly under RSS polling. A small pool spreads the load and degrades more gracefully.
Accounts get suspended for a predictable set of reasons. They are new and have no history, which flags them as automation. They are all created from the same IP address in a short window, which links them together. They generate a pattern of requests no human would produce, such as fetching hundreds of profiles per minute. The platform’s automated systems are specifically tuned to detect exactly the behavior a Nitter instance produces, so suspensions are not a matter of if but when.
Practical measures that extend account life, without eliminating the risk, include:
- Creating accounts gradually rather than in a single batch.
- Keeping cache durations generous so the pool serves fewer upstream requests.
- Limiting how many RSS readers hammer the instance, and how often they poll.
- Rotating in fresh accounts before the pool is fully exhausted, so the instance never goes completely dark.
Keep the session file backed up separately from the accounts themselves, and keep a record of which accounts are in the pool so you can tell which have died. When an account is suspended, remove it from the session file and regenerate the pool. This is the single most common maintenance task you will perform.
Step 5: Launch and verify
Bring the stack up and check the logs.
docker compose up -d
docker compose logs -f nitter
Visit http://your-server:8080 and load a known public profile. If tweets render, the session tokens are valid. If you see errors about failed requests, the tokens have been rejected or the accounts have been limited, which is the most common failure mode today.
A useful verification habit is to test three things in order: a profile page, an individual post, and an RSS feed. These exercise slightly different code paths, and it is common for one to work while another fails after an upstream change. If the RSS feed returns content, the core of what most people want from Nitter is functioning.
curl -s http://your-server:8080/jack/rss | head -n 20
If that returns feed XML rather than an error page, your instance is serving feeds correctly.
Step 6: Add HTTPS
For any real use, put a reverse proxy in front of the container. Caddy is the shortest path because it handles certificates automatically.
nitter.example.com {
reverse_proxy localhost:8080
}
Point your domain’s DNS at the server, and Caddy provisions and renews TLS certificates on its own.
An nginx alternative
If you already run nginx, or you prefer explicit control over the proxy, the following server block achieves the same result. It assumes you have obtained a certificate separately, for example with Certbot.
server {
listen 443 ssl;
server_name nitter.example.com;
ssl_certificate /etc/letsencrypt/live/nitter.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nitter.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name nitter.example.com;
return 301 https://$host$request_uri;
}
Whichever proxy you choose, remember to keep https = false in nitter.conf, because the proxy terminates TLS and forwards plain HTTP to the container. Setting it to true while the proxy already handles HTTPS produces redirect loops and broken links.
Troubleshooting the common failure modes
Self-hosted Nitter fails in a handful of characteristic ways. The following covers the ones you are most likely to hit, in rough order of frequency.
Empty pages or “instance has been rate limited” errors
This is the dominant failure today. It almost always means the session pool is exhausted or the accounts have been suspended. Check the logs for authentication or rate-limit messages. The fix is to regenerate the session file with fresh accounts. If it recurs quickly, your cache durations are too low or too many readers are polling too often, so the pool is being drained faster than it recovers.
Content loads for profiles but not for individual posts, or vice versa
This pattern usually indicates that an upstream change broke one specific parser while leaving others intact. Update to the latest image or fork build, since maintainers typically publish a fix soon after such a break. If no fix exists yet, the feature is simply unavailable until upstream adapts.
Redis connection errors on startup
If the logs show Nitter failing to reach Redis, confirm that redisHost in nitter.conf matches the Redis service name in your Compose file, nitter-redis in these examples. An IP address or localhost will not resolve correctly inside the Docker network. The restart: unless-stopped policy will retry, but a hostname mismatch will never succeed until corrected.
The container starts and immediately exits
Inspect the logs with docker compose logs nitter. The usual causes are a malformed nitter.conf, a missing hmacKey, or a syntax error in the configuration. Because the config is mounted read-only, fix it on the host and restart the stack.
Media images do not load
Media proxying depends on a correct hostname and hmacKey. If images fail while text loads, verify that hostname matches the address you are actually browsing from, since a mismatch invalidates the signed media URLs.
Everything worked yesterday and now nothing does
This is the defining experience of running Nitter after 2024. X changes something on its side, and instances break in unison. Check whether the upstream project has published a new build, update your image, and if the accounts were caught in a suspension wave, regenerate the pool. There is frequently nothing wrong with your configuration at all.
Backup and update procedure
Because the entire instance state is small, backup is simple. Preserve three things: nitter.conf, your docker-compose.yml, and the session file. The Redis volume is a cache and does not need backing up, since it regenerates on demand.
tar czf nitter-backup.tar.gz nitter.conf docker-compose.yml
Store the session file separately and securely, since it contains live account credentials.
To update the instance, pull the newer image and recreate the containers:
docker compose pull
docker compose up -d
Updating is the most common response to an upstream break, so expect to run this more often than with a typical self-hosted application. If an update introduces a regression, pinning the image to the previous known-good tag in your Compose file lets you roll back immediately.
A note on security hardening
Even a personal instance benefits from basic hardening, both to protect the host and to avoid becoming an open relay for others.
- Do not expose port 8080 directly to the internet. Bind the container port to localhost and let the reverse proxy be the only public entry point. In the Compose file,
"127.0.0.1:8080:8080"restricts the mapping to the loopback interface. - Keep the host patched and run Docker as a non-root user in the
dockergroup. - Restrict SSH to key-based authentication and, ideally, a non-standard configuration behind a firewall.
- Treat the session file as a credential. Anyone who obtains it controls the accounts inside it. Restrict its file permissions and never commit it to a repository.
- Consider access controls at the proxy, such as HTTP basic authentication or an allowlist, if the instance is meant only for you. A public instance invites traffic that will exhaust your account pool almost immediately.
The honest maintenance reality
A self-hosted Nitter is no longer a set-and-forget project. Expect to:
- Replace banned accounts in the session pool regularly.
- Update the image whenever X changes its internal endpoints and breaks parsing.
- Monitor for silent failures where pages load but content is stale or empty.
For a single developer who wants a private reader and enjoys the tinkering, that may be acceptable. For anything mission-critical, the fragility is disqualifying. The instance you stand up this weekend may work perfectly and then serve nothing but errors a month from now, through no fault of your configuration. That unpredictability is the defining characteristic of the current era of Nitter, and no guide can engineer it away.
Frequently asked questions
Is self-hosting Nitter legal?
Running the software on your own server is not itself the issue. Accessing X’s internal endpoints with automated account sessions violates the platform’s terms of service, which is why the accounts get suspended. Understand that you are operating against the platform’s wishes and on infrastructure and credentials you should be prepared to lose.
Do I still need account tokens if I only want RSS feeds?
Yes. RSS feeds are generated from the same upstream data as the web interface, so they depend on the same authenticated sessions. There is no read path left that avoids the account requirement.
How many accounts do I need in the pool?
There is no fixed answer, because it depends on how much traffic and how many feeds the instance serves and how aggressively the platform is suspending accounts at the time. A single account is enough to verify the setup works. Sustained use generally requires several, with fresh ones ready to rotate in.
Why did my instance work for a while and then stop?
Almost always one of two reasons: the accounts in your pool were suspended, or X changed its internal endpoints and broke the parser. The first is fixed by regenerating the pool, the second by updating to a build that adapts to the change. Both are routine rather than exceptional.
Can I run this without Docker?
Yes, Nitter can be built and run directly, but Docker is the most reproducible way to manage the application and its Redis dependency together. The trade-offs discussed here are identical regardless of how you run it, because they stem from the platform, not the packaging.
Is it worth self-hosting Nitter at all in the current climate?
For a hobbyist who values the private reading experience and does not mind ongoing maintenance, it can be. For anyone who needs reliable, unattended access to public content, the fragility makes it a poor foundation, and the durable alternative below is a better investment of effort.
A more durable path
If your goal is not Nitter specifically but the outcomes it provided, reading public content privately, monitoring accounts, and pulling structured feeds, then a tool tied to X’s undocumented internals is the wrong foundation. It will break again on the platform owner’s schedule, not yours.
The sustainable approach is to own the layer you depend on: aggregate the sources you care about into a platform you control, expose your own feeds, and stop betting your workflow on access that can vanish overnight. That is the subject of a companion article on building your own monitoring stack.