The Consent Illusion: Who Really Runs Luxembourg’s Web
After GDPR, Luxembourg’s web sprouted cookie banners — but only on about one site in three. Meanwhile nearly three in four .lu sites quietly load Google. By parsing the raw HTML of 83,000 website-years, I map the trackers they embed, the banners they show, and the handful of foreign companies the local web depends on.
Every time you open a website, your browser quietly fetches resources from other companies’ servers — analytics scripts, advertising pixels, fonts, maps, embedded videos. You never see them, but many of them see you. So I parsed the raw HTML of every .lu website-year in the sample and recorded three things: the third parties each site connects you to, whether it shows a cookie-consent banner, and who owns those third parties.
NoteThe Dataset
8,061 Luxembourg websites in 2024, parsed from raw HTML. The median site reaches out to 3 third-party domains, 71.5% embed at least one Google service — yet only 31.2% show a cookie-consent banner.
The Consent Illusion
In May 2018 the GDPR came into force and cookie banners began to bloom. The promise was control: you would be asked before being tracked. So did the banner and the tracker arrive together — or did the tracking quietly outrun the consent that was meant to gate it?
Cookie banners vs. actual tracking, 2013–2024
Show code
years = over_time['years']fig = go.Figure()# Cookie-banner prevalence (bars)fig.add_trace(go.Bar( x=years, y=over_time['cmp_pct'], name='Shows a cookie banner', marker_color='rgba(120, 120, 120, 0.30)', marker_line=dict(width=0), hovertemplate='<b>%{x}</b><br>%{y:.1f}% show a cookie banner<extra></extra>',))# Sites embedding a dedicated tracker (line)fig.add_trace(go.Scatter( x=years, y=over_time['any_tracker_pct'], name='Loads a dedicated tracker', mode='lines+markers', line=dict(color='#b2182b', width=3, shape='spline', smoothing=0.6), marker=dict(size=6), hovertemplate='<b>%{x}</b><br>%{y:.1f}% load a tracker<extra></extra>',))# Sites embedding any Google service (line)fig.add_trace(go.Scatter( x=years, y=sovereignty['google_pct'], name='Embeds a Google service', mode='lines+markers', line=dict(color='#1a1a1a', width=3, shape='spline', smoothing=0.6), marker=dict(size=6), hovertemplate='<b>%{x}</b><br>%{y:.1f}% embed Google<extra></extra>',))fig.add_vline( x=GDPR_YEAR, line_dash='dash', line_color='#444', line_width=1.5, annotation_text='GDPR in force', annotation_position='top left', annotation_font_color='#444',)fig.update_layout( height=460, margin=dict(t=30, r=20, b=100, l=60), plot_bgcolor='white', paper_bgcolor='white', xaxis=dict(title='Year', dtick=1, gridcolor='#eee', zerolinecolor='#eee'), yaxis=dict(title='% of websites', ticksuffix='%', range=[0, 100], gridcolor='#eee', zerolinecolor='#eee'), legend=dict(orientation='h', y=-0.28, x=0.5, xanchor='center'), hovermode='x unified', bargap=0.3,)fig.show(config={'displayModeBar': False})
Note: “Dedicated tracker” = an embedded domain classified as analytics, advertising, social, tag-manager or marketing. “Google service” is broader — it also counts fonts, maps and reCAPTCHA, which still send the visitor’s IP to Google. Cookie-banner detection looks for named consent platforms and is a lower bound.
TipKey Findings
This is the consent illusion. By 2024, a detectable cookie banner appeared on only 31.2% of sites — yet 43.1% loaded a dedicated tracker and 71.5% embedded Google. The data you would want to consent to is more than twice as common as the consent box itself — and even the narrowest count, sites loading a dedicated tracker (43.1%), outnumbers the visible banners.
And consent never caught up with tracking. Google’s reach more than doubled across the decade and peaked after GDPR; even after slipping back from its 2020 high it still dwarfs the banner rate. The banner became a ritual layered on top of data flows that GDPR made visible and consented-to, not measurably lighter.
The one unambiguous improvement of the decade was security, not privacy: HTTPS went from 0% of sites in 2013 to 86% in 2024.
The Consent We Can’t See
There is an honest objection to the chart above: a static read of the HTML only catches banners written into the page. A consent prompt injected at load time through Google Tag Manager — a script that can fire trackers and pop up banners after the page loads — leaves no fingerprint in the archive; the markup shows only googletagmanager.com. So the 31.2% is a floor. How high could the true banner rate be?
The consent blind spot: Google Tag Manager vs. visible banners, 2013–2024
Show code
blindspot = data['cmp_blindspot']bs_years = blindspot['years']fig = go.Figure()fig.add_trace(go.Scatter( x=bs_years, y=over_time['cmp_pct'], name='Detected cookie banner', mode='lines+markers', line=dict(color='#2166ac', width=3, shape='spline', smoothing=0.6), marker=dict(size=6), hovertemplate='<b>%{x}</b><br>%{y:.1f}% have a detectable banner<extra></extra>',))fig.add_trace(go.Scatter( x=bs_years, y=blindspot['gtm_pct'], name='Loads Google Tag Manager', mode='lines', line=dict(color='#9aa0a6', width=2, dash='dot', shape='spline', smoothing=0.6), hovertemplate='<b>%{x}</b><br>%{y:.1f}% load GTM<extra></extra>',))fig.add_trace(go.Scatter( x=bs_years, y=blindspot['gtm_no_cmp_pct'], name='Runs GTM with no banner we can see', mode='lines+markers', line=dict(color='#b2182b', width=3, shape='spline', smoothing=0.6), marker=dict(size=6), hovertemplate='<b>%{x}</b><br>%{y:.1f}% run GTM, no detected banner<extra></extra>',))fig.add_vline( x=GDPR_YEAR, line_dash='dash', line_color='#444', line_width=1.5, annotation_text='GDPR in force', annotation_position='top left', annotation_font_color='#444',)fig.update_layout( height=440, margin=dict(t=30, r=20, b=100, l=60), plot_bgcolor='white', paper_bgcolor='white', xaxis=dict(title='Year', dtick=1, gridcolor='#eee', zerolinecolor='#eee'), yaxis=dict(title='% of websites', ticksuffix='%', range=[0, 45], gridcolor='#eee', zerolinecolor='#eee'), legend=dict(orientation='h', y=-0.30, x=0.5, xanchor='center'), hovermode='x unified',)fig.show(config={'displayModeBar': False})
Note: “No banner we can see” means no named consent platform was found in the archived HTML, even though the site loads Google Tag Manager — which can inject a banner (or a tracker) at runtime. This is an upper bound on the consent we may be missing, not proof that these sites ask nothing.
TipKey Findings
Even the most generous accounting leaves the illusion intact. Take the detected banners (31.2%) and add every site that runs Google Tag Manager without one we can see (13.7%) — assuming, charitably, that all of them inject a banner at load time — and you reach about 45%. That ceiling still sits well below the 71.5% that load Google. Even the loosest possible measure — any cookie-related text anywhere on the page (65.6%) — does not catch up.
However you count it, more of Luxembourg’s web quietly sends your data than visibly asks your permission.
Whose Servers Are You Really Talking To?
When a Luxembourg website loads, where do its third-party requests point? I mapped every embedded domain I could identify to the country of its owner. A .lu third party counts as local; everything else is foreign infrastructure the site leans on.
Owner country of identified third parties, 2013–2024
Show code
sov_years = sovereignty['years']shares = sovereignty['country_share_identified']fig = go.Figure()for country in ['Other', 'LU', 'EU', 'US']: fig.add_trace(go.Scatter( x=sov_years, y=shares[country], name=country_names[country], mode='lines', stackgroup='one', line=dict(color=country_colors[country], width=0), fillcolor=country_colors[country], hovertemplate=f"<b>{country_names[country]}</b><br>%{{y:.1f}}% of identified third parties<extra></extra>", ))fig.update_layout( height=460, margin=dict(t=20, r=20, b=100, l=60), plot_bgcolor='white', paper_bgcolor='white', xaxis=dict(title='Year', dtick=1, gridcolor='#eee', zerolinecolor='#eee'), yaxis=dict(title='% of identified third parties', ticksuffix='%',range=[0, 100], gridcolor='#eee', zerolinecolor='#eee'), legend=dict(orientation='h', y=-0.28, x=0.5, xanchor='center'), hovermode='x unified',)fig.show(config={'displayModeBar': False})
Note: Shares are computed over third parties that could be attributed to a known owner; about 30% of all third-party connections in 2024 could not be attributed and are excluded from this chart.
TipKey Findings
Luxembourg’s web runs on someone else’s infrastructure. Of the third parties I could identify in 2024, 86.1% are US-owned against just 2.9% local. Only 6.2% of .lu sites embed any Luxembourg-hosted third party at all.
For a country that talks about digital sovereignty, the web tells a blunter story: open almost any .lu site and you are, within milliseconds, talking to California.
The Companies Inside Luxembourg’s Web
Which specific companies sit inside the most sites?
Most-embedded third-party companies (2024)
Show code
top =list(reversed(top_entities))labels = [e['entity'] for e in top]vals = [e['pct_sites'] for e in top]bar_colors = [country_colors.get(e['country'], '#9aa0a6') for e in top]fig = go.Figure(go.Bar( y=labels, x=vals, orientation='h', marker=dict(color=bar_colors), text=[f'{v:.0f}%'for v in vals], textposition='outside', customdata=[[country_names.get(e['country'], e['country']),'tracker'if e['tracker'] else'functional'] for e in top], hovertemplate='<b>%{y}</b><br>%{x:.1f}% of sites<br>%{customdata[0]} · %{customdata[1]}<extra></extra>',))fig.update_layout( height=max(420, len(top) *28), margin=dict(t=20, r=70, b=50, l=160), plot_bgcolor='white', paper_bgcolor='white', xaxis=dict(title='% of websites embedding this company', ticksuffix='%', gridcolor='#eee', zerolinecolor='#eee'), yaxis=dict(tickfont=dict(size=11)), showlegend=False,)fig.show(config={'displayModeBar': False})
Note: Bars are colored by owner country (■ US, ■ EU, ■ Luxembourg, ■ other). A site counts once per company.
TipKey Findings
One company stands apart. Google reaches 71.5% of all .lu sites — more than four times as common as any other company, and nearly ten times the reach of Meta (7.5%), the largest social tracker after it. Crucially, much of Google’s reach is not ad-tracking but fonts, maps and reCAPTCHA — services a developer adds without a second thought. Yet even embedding Google Fonts hands the visitor’s IP address to Google, which a German court ruled a GDPR violation in 2022. On Luxembourg’s web, you don’t have to be advertised to in order to be exposed; you just have to load the page.
Do Essential Services Track You Too?
Reusing the sector labels from my topic-modelling post, I can ask whether the sites you have to use — your commune, your doctor, your child’s crèche — handle your data more carefully than the market does.
Share of sites embedding a US-owned tracker, by sector (2024)
Show code
sectors = [s for s in by_sector if s['n_sites'] >=5]sectors_sorted =sorted(sectors, key=lambda s: s['us_tracker_pct'])names = [s['sector'] for s in sectors_sorted]vals = [s['us_tracker_pct'] for s in sectors_sorted]essential =set(ess_vs_mkt['essential_sectors'])bar_colors = ['#2166ac'if n in essential else'#b2182b'for n in names]fig = go.Figure(go.Bar( y=names, x=vals, orientation='h', marker=dict(color=bar_colors), text=[f'{v:.0f}%'for v in vals], textposition='outside', hovertemplate='<b>%{y}</b><br>%{x:.1f}% embed a US tracker<extra></extra>',))fig.update_layout( height=max(420, len(names) *30), margin=dict(t=20, r=70, b=50, l=160), plot_bgcolor='white', paper_bgcolor='white', xaxis=dict(title='% of sites embedding a US-owned tracker', ticksuffix='%', gridcolor='#eee', zerolinecolor='#eee'), yaxis=dict(tickfont=dict(size=11)), showlegend=False,)fig.show(config={'displayModeBar': False})
Note: ■ essential services (Public Services, Healthcare, Childcare), ■ market-driven and other sectors. “US-owned tracker” = an analytics/advertising/social/tag-manager domain owned by a US company.
TipKey Findings
Needing a service does not protect you. Essential-service sites embed a US tracker on 46.0% of pages — slightly more than market-driven sites (43.6%), not less. Childcare is the most tracked sector of all (60% embed a US tracker, 90% load Google), even as it became one of the least multilingual. Public-service sites show the most cookie banners (58%) — the institutions most bound by the law are the ones that ask — but they still hand you to the same foreign platforms.
Methodology
This analysis parses the raw HTML behind the same CommonCrawl sample used across the series — no new models are trained; the value is in reading what the markup reveals about a site’s infrastructure.
1. Third-Party Extraction
For each website-year I parse every archived page and collect the external hosts it loads resources from — src attributes (<script>, <img>, <iframe>, <source>), <link>hrefs, and absolute URLs inside inline scripts. Navigation links (<a href>) are deliberately excluded so “follow us on Facebook” is not miscounted as an embedded third party. A third party is any host whose registrable domain (eTLD+1) differs from the site’s own. I also flag named cookie-consent platforms and HTTPS.
2. Entity & Country Mapping
Each third-party domain is matched against a curated, version-controlled entity map of the dominant web companies, giving an owning entity, owner country (US / EU / LU / Other), and a category. Any .lu third party counts as local. Trackers (analytics, advertising, social, tag managers, marketing) are separated from functional infrastructure (CDNs, fonts, payments, maps, video). Domains not in the map are reported as unidentified and never assumed to be trackers — a deliberately conservative count, and the reason the sovereignty chart shows only the share that could be attributed.
3. Aggregation
Per website-year I compute third-party counts, the owner-country mix, and big-tech reach, inner-joined to the language sample (same universe as the other posts) and to the BERTopic sector mapping for the sector and essential-vs-market cuts.
Note: This measures third parties declared in the archived HTML, not live network traffic. CommonCrawl archives only a handful of pages per site and captures markup before JavaScript runs, so scripts injected at runtime (and many banners) are undercounted — every tracker figure here is a lower bound.
Citation
For attribution, please cite this work as:
Garbers, J. (2026, June). The Consent Illusion: Who Really Runs Luxembourg's Web.
Retrieved from https://github.com/julio-garbers/blog/tree/main/web_trackers_lux