Client-side vs server-side bidding: when to move to Prebid Server
Every client-side bidder adds network requests and JavaScript work to the page, and that cost grows with each partner you add. Moving bidders to Prebid Server takes that work off the browser, but server-side bidders see fewer user IDs, so some of them bid less. For most publishers the answer is a hybrid: keep the bidders that depend most on browser identity client-side, move the long tail to the server, and prove the split with an A/B test before you commit.
Versions checked for this post: Prebid.js 11.44.0, PBS Go 4.11.0 and PBS Java 4.4.0, all released in the last week of October 2026.
How does s2sConfig route bidders to Prebid Server?
The Prebid Server Bid Adapter (prebidServerBidAdapter) is a meta-adapter. It is not a bidder. Prebid.js takes every bidder listed in s2sConfig.bidders, builds one OpenRTB request for them and sends it to your Prebid Server (PBS) cluster. PBS calls each bidder server-to-server, collects the bids and returns them to the page. From there Prebid.js sets ad server targeting exactly as it does for client-side bids.
Bid params do not change. Prebid.js forwards the same params you already use on the page. One warning from the Prebid docs is worth repeating: errors in bidder params make Prebid Server reject the entire request, not just the bad bidder.
A minimal working config looks like this:
pbjs.setConfig({
s2sConfig: [{
accountId: '1001',
enabled: true,
bidders: ['bidderA', 'bidderB', 'bidderC'],
endpoint: 'https://pbs.example.com/openrtb2/auction',
syncEndpoint: 'https://pbs.example.com/cookie_sync',
timeout: 700
}]
});
The keys that matter, checked against the Prebid.js 11.44 source:
| Key | What it does | What to know |
|---|---|---|
accountId | Your account on the PBS host | Required. PBS uses it to load account-level config. |
enabled | Turns the block on | Defaults to false. Without enabled: true the block is skipped with a warning. |
bidders | Bidder codes to run server-side | If omitted, only ad units with no bidders (stored impressions) go to PBS. |
endpoint | The /openrtb2/auction URL | Required. A string, or an object with p1Consent and noP1Consent URLs. |
syncEndpoint | The /cookie_sync URL | Not validated, but without it Prebid.js never starts server-side user syncs. |
timeout | tmax sent to PBS | If unset: 75% of the auction timeout or maxTimeout (default 1500 ms), whichever is lower. |
adapter | Which adapter talks to PBS | Defaults to 'prebidServer'. Leave it alone. |
Two notes. First, older guides use defaultVendor to fill in a named host’s endpoints. In 11.44 there are no vendor presets left: validation checks only accountId and endpoint, and defaultVendor survives only as a label in internal metrics and in old s2sTesting examples. Set your endpoints explicitly. Second, the docs table still describes a 750 ms cap on the default timeout, while the current source uses maxTimeout. Set timeout yourself. Prebid recommends a value 200 to 300 ms below your Prebid.js timeout, so server bids arrive before the auction closes.
You can define s2sConfig as an array to use more than one PBS cluster. The same bidder cannot appear in two blocks; Prebid.js keeps it in the first and drops it from the rest.
What do you gain by moving bidders server-side?
Fewer requests from the browser. Each client-side bidder sends its own request from the page. Server-side bidders share one request to PBS. User syncs still run in the browser (more on that below), so the saving is not total, but the auction itself gets much lighter on the device.
More bidders without more page weight. Once PBS is in place, adding a demand partner is a line in bidders, not another round trip from every page view. Bidders that only run server-side do not need their client adapter in your Prebid.js build, unless you still run them client-side somewhere, such as in an A/B test arm.
One auction for every surface. The same PBS cluster serves Prebid.js, AMP pages, Prebid Mobile apps and server-side cases such as CTV and long-form video. Bidder setup, floors and account config live in one place instead of one per surface. Prebid’s AMP and Prebid Mobile integrations both run their auctions through PBS, so if you plan to monetise those you need it anyway.
Larger server-side bidder pool. The Prebid Server overview lists more than 230 server-side adapters. Check the Prebid Server bidder list for each partner before you plan a move: not every Prebid.js adapter has a server-side equivalent, and media type support can differ.
What do you lose?
Cookie match rates. This is the big one. A client-side bidder reads its own cookie in the user’s browser. A server-side bidder only sees what Prebid Server sends it, and PBS stores bidder IDs in a single uids cookie on the PBS host domain. That cookie is filled through the /cookie_sync flow: Prebid.js calls the endpoint as soon as s2sConfig is parsed, PBS replies with sync URLs, and each bidder redirects back to /setuid with its ID.
That flow depends on third-party cookies on the PBS domain. Safari has blocked cross-site cookies by default since March 2020, and consent rules limit syncing in regulated regions. The Prebid Server FAQ is frank about it: Prebid.js-initiated match rates are “about what can be expected given the constraints”. Two levers help:
userSyncLimitandcoopSyncons2sConfig. Cooperative syncing (on by default) lets PBS sync bidders that are not on the current page, so IDs are ready for the next one.- User ID modules. Prebid.js writes their output to
user.ext.eidsin the request it sends to PBS, so bidders that read eids server-side are not relying on theuidscookie alone.
Bid density for some bidders. Fewer recognised users means some bidders bid less often or lower when moved server-side. How much varies by bidder and by your traffic mix, which is why you test bidder by bidder instead of moving everything at once.
Someone has to run the servers. Prebid’s own hosting guide says PBS “is not a turn-key solution”. Whoever hosts it takes on production servers, load balancers, Prebid Cache with a NoSQL store, a replicated database for accounts and stored requests, monitoring, and at least quarterly upgrades. Prebid Server does not terminate SSL itself; it expects a load balancer or proxy in front. Your options are to run it yourself, as BidCliq does for the publishers it works with, or to use one of the managed services listed by Prebid.org. Either way it is a line in your budget that client-side bidding does not have.
Which bidders should stay client-side?
There is no rule in the Prebid docs for this, so treat the following as a starting framework, not a formula:
- Keep client-side the bidders that account for most of your header bidding revenue, and any bidder whose server-side win rate drops sharply in testing. These are usually the partners most sensitive to their own cookie.
- Keep client-side any bidder without a server-side adapter, or whose server adapter does not support a media type you need.
- Move server-side the long tail: bidders that win rarely but still cost a request on every page view.
- Move server-side anything you also want on AMP or in apps, so one config serves every surface.
If you need the same bidder on both paths, use a server-side alias. List the alias, for example tripleliftVideo, in s2sConfig.bidders, and map it to the real adapter with extPrebid: { aliases: { tripleliftVideo: 'triplelift' } }. The Prebid docs use exactly this pattern to run a bidder client-side for banner and server-side for video. Register the alias’s gvlMapping with setConfig too, as the docs advise.
How do you test the move?
Use the s2sTesting module. It ships with Prebid.js as an optional module you add to your build, and Prebid expects you to remove it once the test is over.
pbjs.setConfig({
s2sConfig: {
accountId: '1001',
enabled: true,
testing: true,
bidders: ['bidderA', 'bidderB'],
endpoint: 'https://pbs.example.com/openrtb2/auction',
syncEndpoint: 'https://pbs.example.com/cookie_sync',
bidderControl: {
bidderA: { bidSource: { server: 10, client: 90 }, includeSourceKvp: true },
bidderB: { bidSource: { server: 10, client: 90 }, includeSourceKvp: true }
}
},
targetingControls: { addTargetingKeys: ['SOURCE'] }
});
testing: true switches on bidderControl. The bidSource values are weights, not percentages. With includeSourceKvp and the SOURCE targeting key, each bidder sends hb_source_bidderA=client or hb_source_bidderA=s2s to the ad server, so you can split results by path in reporting. You can also override the weights per ad unit with bidSource on an individual bid.
Two details from the module source affect how you read results:
- The split is per page view, not per user. The module draws one random number per page load and uses it for every bidder, so if two bidders are both at 10% server, it is the same 10% of page views. The same user will land in both arms over a session.
testServerOnly: trueisolates the server path. When a page view lands in a server arm, all client requests are suppressed. The docs warn this can cost revenue and suggest keeping the server share at 5% or less.
What to watch, per bidder and per arm:
- Bid rate and win rate from your analytics adapter.
- Revenue per thousand page views (RPM), not just eCPM. A higher eCPM on fewer bids can still lose money.
- Ad server impressions and revenue split by
hb_source. - Bid latency and timeouts. If server bids miss the auction, lower
timeoutor raise your Prebid.js timeout. - Match rates, from the bidders themselves. The
s2sTestingdocs suggest asking SSPs for cookie match data during the test. - Page speed on the test arm: request count, main-thread time and your Core Web Vitals.
Run each step long enough to cover your weekly traffic cycle, then move the next group of bidders.
Checklist
- Confirm each bidder has a server-side adapter that supports your media types.
- Set
enabled: true,accountId,endpoint,syncEndpointand an explicittimeoutins2sConfig. Do not rely ondefaultVendor. - Turn on User ID modules so eids reach Prebid Server.
- Start with long-tail bidders. Keep your top partners client-side until data says otherwise.
- A/B test with
s2sTestingandhb_source, comparing RPM, win rate and latency per bidder. - Decide who runs PBS, and budget for hosting, upgrades and the account and stored request data behind it.
Frequently asked questions
Does moving a bidder to Prebid Server change its bid params?
No. Prebid.js sends the same params you use client-side to Prebid Server. But Prebid Server rejects the whole request if any bidder's params are invalid, so test every bidder after the move.
What is the default Prebid Server timeout in Prebid.js?
If s2sConfig.timeout is not set, Prebid.js 11.44 sends a tmax of 75% of the auction timeout or maxTimeout (1500 ms by default), whichever is lower. Prebid recommends setting it 200 to 300 ms below your Prebid.js timeout.
Why do server-side bidders sometimes bid less?
Server-side bidders only see user IDs that Prebid Server has synced into its own uids cookie, or IDs Prebid.js passes as eids. Where third-party cookies are blocked or consent limits syncing, fewer users are recognised and some bidders bid less often or lower.
Can the same bidder run client-side and server-side at once?
Yes, using a server-side alias. You list the alias in s2sConfig.bidders and map it to the real adapter with extPrebid.aliases, for example to run a bidder client-side for banner and server-side for video.
How do I A/B test Prebid Server against client-side bidding?
Add the s2sTesting module, set testing: true and a bidSource weight per bidder in bidderControl, and turn on the SOURCE targeting key. The hb_source key then lets you split revenue and win rate by path in your ad server.

About the author
Founder, BidCliq
Sharad Yadav is the founder of BidCliq, a publisher-first ad monetization platform that helps publishers and ad ops teams run header bidding, Prebid and programmatic across display, video, app and CTV. Before BidCliq, he led AdTech and product at GeeksforGeeks, where he built an in-house ads product that doubled programmatic revenue within nine months and an anti-adblock solution that lifted ad yield by 10%. He is a founding member of AdCP.