<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>favicon &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/favicon/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Sun, 21 Sep 2025 20:20:57 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>favicon &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[A Self-Hosted Favicon Proxy written in PHP]]></title>
		<link>https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/</link>
					<comments>https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 28 Oct 2025 12:34:54 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[favicon]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[php]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=63434</guid>

					<description><![CDATA[In theory, you should be able to get the base favicon of any domain by calling /favicon.ico - but the reality is somewhat more complex than that. Plenty of sites use a wide variety of semi-standardised images which are usually only discoverable from the site&#039;s HTML.  There are several services which allow you to get favicons based on a domain. But they all have their problems.   Google   Exposes…]]></description>
										<content:encoded><![CDATA[<p>In theory, you should be able to get the base favicon of any domain by calling <code>/favicon.ico</code> - but the reality is somewhat more complex than that. Plenty of sites use a wide variety of semi-standardised images which are usually only discoverable from the site's HTML.</p>

<p>There are several services which allow you to get favicons based on a domain. But they all have their problems.</p>

<ul>
<li><a href="https://www.google.com/s2/favicons?domain=shkspr.mobi&amp;sz=256">Google</a>

<ul>
<li>Exposes your user's to Google's tracking.</li>
<li>Relies on redirects.</li>
</ul></li>
<li><a href="https://icons.duckduckgo.com/ip9/shkspr.mobi.ico">DuckDuckGo</a>

<ul>
<li>Not officially supported by DDG.</li>
</ul></li>
<li><a href="https://favicon.is/shkspr.mobi">Favicon.is</a>

<ul>
<li>No privacy policy whatsoever.</li>
</ul></li>
<li><a href="https://icon.horse/">Icons.horse</a>

<ul>
<li>Paid service.</li>
<li>Only small size icons.</li>
</ul></li>
<li><a href="https://favicone.com/shkspr.mobi">Favicone</a>

<ul>
<li>No privacy policy.</li>
<li>Only small size icons.</li>
</ul></li>
</ul>

<p>I want to show favicons next to specific links, but I don't want to expose my visitors to unnecessary tracking. How can I proxy these images so they are stored and served locally?</p>

<p>There are a few existing services. Some use <a href="https://github.com/seadfeng/favicons-proxy">Cloudflare workers</a> or other <a href="https://github.com/shaklain125/gicon">cloud services</a>, there are some local-first ones which are <a href="https://github.com/toolness/favicon-proxy">unmaintained</a>.  But nothing modern, self-hosted, and as easy to deploy as uploading a single PHP file.</p>

<p>So here's my attempt to make something which will preserve user privacy, be reasonably fast, and have moderately up-to-date icons, while remaining fast and efficient.</p>

<p></p><nav role="doc-toc"><menu><li><h2 id="table-of-contents"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#table-of-contents">Table of Contents</a></h2><menu><li><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-domain">Getting the domain</a></li><li><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-image">Getting the image</a></li><li><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-structure-right">Getting the structure right</a></li><li><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#preventing-abuse">Preventing abuse</a></li><li><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#putting-it-all-together">Putting it all together</a></li></menu></li></menu></nav><p></p>

<h2 id="getting-the-domain"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-domain">Getting the domain</a></h2>

<p>Assuming the request comes in to <code>https://proxy.example.com/?domain=bbc.co.uk</code></p>

<p>PHP has a <a href="https://www.php.net/manual/en/filter.constants.php#constant.filter-validate-domain">handy <code>FILTER_VALIDATE_DOMAIN</code> filter</a> which will determine if the string is a domain.</p>

<pre><code class="language-php">filter_var( $domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME );
</code></pre>

<h3 id="dealing-with-idns"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#dealing-with-idns">Dealing with IDNs</a></h3>

<p>Some domains contain non-ASCII characters - for example <a href="https://莎士比亚.org/">https://莎士比亚.org/</a> - not all favicon services support International Domain Names.</p>

<p>Using <a href="https://www.php.net/manual/en/function.idn-to-ascii.php">the <code>idn_to_ascii()</code> function</a>, it is possible to get the Punycode domain.</p>

<pre><code class="language-php">$domain = idn_to_ascii("莎士比亚.org");
</code></pre>

<h2 id="getting-the-image"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-image">Getting the image</a></h2>

<ol>
<li>Check if the icon has previously been downloaded.</li>
<li>Rotate randomly between a few different Favicon services.</li>
<li>Download the icon.</li>
<li>Save it somewhere.</li>
</ol>

<h2 id="getting-the-structure-right"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#getting-the-structure-right">Getting the structure right</a></h2>

<p>I know from my work on OpenBenches that storing tens of thousands of files in a single directory can be problematic. So I'll store the retrieved favicon in: <code>/tld/domain/subdomain/</code></p>

<p>That will make it quick to see if an icon exists. I'll save the file with a filename based on the current timestamp. That will allow me to check if an icon is out of date, and will prevent people downloading the icons directly from me.</p>

<h2 id="preventing-abuse"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#preventing-abuse">Preventing abuse</a></h2>

<p>I don't want anyone but visitors to my site to be able to use this service. So I'll add a (weak) check to see if the request came from my domain.</p>

<pre><code class="language-php">$referer = parse_url( $_SERVER["HTTP_REFERER"], PHP_URL_HOST );
if ( $referer == "shkspr.mobi") {
   …
}
</code></pre>

<p>Some browsers may not send referers for privacy reasons. So they won't see the favicons. But they probably wouldn't have seen the images loaded from a 3<sup>rd</sup> party service. So I'll serve a default image.</p>

<h2 id="putting-it-all-together"><a href="https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/#putting-it-all-together">Putting it all together</a></h2>

<p>You can grab the code from <a href="https://git.edent.tel/edent/Favicon-Proxy-PHP">my personal git service</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=63434&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/10/a-self-hosted-favicon-proxy-written-in-php/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>
