I went down a rabbit hole trying to add a “favicon” (Web page icon) to an ESP32 web server project.
No one seems to have a complete answer, so here you go!
Here is a step-by-step walk-through.
Table of Contents
Summary
Building a web server on ESP32 is super simple, and is a great way to build a quick and dirty UI for most projects. In fact, I’m using ESP32 as the basis for v2 of my gate opener web interface.
HOWEVER, when you open the site in a browser, it makes a request for favicon, which is the “Favorite” icon – the website’s icon that will also be displayed in your bookmarks (used to be called “favorites”) list.
Since there are no files, the request to “/favicon.ico” fails with 404.
![]()
I don’t know why this bugged me, LOL! But I thought about it a bit and decided I wanted an icon.
Step by Step Solution
Overview – add an icon <LINK> reference containing an inline, base64-encoded PNG to the document header.
- Go design / borrow / draw / photograph your website icon. 48×48 pixels is an appropriate resolution. Save as PNG format or convert to PNG. The rest of the steps assume the icon file is saved as favicon.png.
- Convert to base64.
base64 favicon.png > favicon.h
- Add favicon.h to your project. Be sure to add “include” to your main INO file:
#include "favicon.h"
- Edit the base64 file (favicon.h) to add a variable declaration and HTML syntax:
const String favicon = F(R"=====(<link href="data:image/x-icon;base64, iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAwnpUWHRSYXcgcHJvZmlsZSB0eXBl IGV4aWYAAHjabVDBEcMgDPt7io4AyBB7HNKkd92g49eAk4a2ukMRlk8xpv31fNCtIUUmzosULSUY WFlTNSFhoHaOgTt3wC27T3U6jWQlfDqleP9Rj2fA+FRT+RIkdzfW2VD2fPkKSj5Zm6jpzYPUg5CG ET2gjmeForJcn7DuYYaMQ41Y5rF/7ottb8v2H6S0IyIYAzwGQDuFUE2gs1qjNZlmqDGgHmYL+ben A/QG3cVZGvmEWAMAAAGFaUNDUElDQyBwcm9maWxlAAB4nH2Rv0vDQBzFX1ulRSoOVhB1yFB1sYuK ONYqFKFCqBVadTC59Bc0aUhSXBwF14KDPxarDi7Oujq4CoLgDxD/AHFSdJESv5cUWsR4cNyHd/ce d+8Af6PCVLMrDqiaZaSTCSGbWxWCrwgghAGMY1hipj4niil4jq97+Ph6F+NZ3uf+HL1K3mSATyCO M92wiDeIZzYtnfM+cYSVJIX4nHjCoAsSP3JddvmNc9FhP8+MGJn0PHGEWCh2sNzBrGSoxNPEUUXV KN+fdVnhvMVZrdRY6578heG8trLMdZojSGIRSxAhQEYNZVRgIUarRoqJNO0nPPxDjl8kl0yuMhg5 FlCFCsnxg//B727NwtSkmxROAN0vtv0xCgR3gWbdtr+Pbbt5AgSegSut7a82gNlP0uttLXoE9G0D F9dtTd4DLneAwSddMiRHCtD0FwrA+xl9Uw7ovwV61tzeWvs4fQAy1FXqBjg4BMaKlL3u8e5QZ2// nmn19wOXfXK13okkWAAADXhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6 eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6 UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5z IyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRw Oi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5h ZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOmRjPSJodHRw Oi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cu Z2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8x LjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBN TTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6Mzk0OTdlOTgtMDNlNy00YzhjLWE3OTUtYzA3 MTU0YmMzMjNhIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjBkOGIwMDU1LWY1OGQtNDMx Mi1iZjQyLTJiMzEwODc1NjJmOCIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlk Ojk1OGM2YjAzLTc5NmUtNDlkOC04NTE0LTczMzdmNDBiNWMzMiIKICAgZGM6Rm9ybWF0PSJpbWFn ZS9wbmciCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1Q OlRpbWVTdGFtcD0iMTc3MzA3MTM3OTY2MDIwOCIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM0Igog ICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCIKICAg eG1wOk1ldGFkYXRhRGF0ZT0iMjAyNjowMzowOVQxMDo0OTozOS0wNTowMCIKICAgeG1wOk1vZGlm eURhdGU9IjIwMjY6MDM6MDlUMTA6NDk6MzktMDU6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAg IDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAg c3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpkMjlmYzAw Ni03Mjk1LTQ3OTYtYmU3YS1kNTM3MTFkODAxYzIiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9 IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDI2LTAzLTA5VDEwOjQ5OjM5 LTA1OjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNj cmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PmARca0AAADq UExURfvUM////wAAABQVGAAAFv/YNP/aNP/cNfvTKfvSHf/bNPvRFfvSJPvTLQAAFRETGPvXRP3o n/7xxvzihP/99/zjif/66v700v744/bQMvzkjvzgePvVOPvaWv733v/++v3rrQsPGPzebP3po9e2 LpmCJv3uu7qeKgAJF8OlKP7yyf701PvZUOrGMSwoGk1DHeXBLjsyDF9RE3NhFx0dGUQ7HIx3JKmP KGlaIFlMHn1rIvzecSchCIFtGhEOA413HC0mCc+vKjozG1NIHbCWKTArGiUjGXNiIUc8Dh0YBr6g JlJGEDQsC0I4DYFzfZMAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH 6gMJDzEnKahS+gAAAcZJREFUSMetlm1vwiAQx0/4AJqW1KAkrYsP2YTE2fiQNLUh+kLnvv/nWWun BUprcfu/Anq/chzHAfRMkWE0Sjhjg7dRNCS1z2BYez4DRcz3SAtAIg418Yg0AOHcYn5D5qENCHxo lB/UgSWHFvGlCXgMWsU8HXhmXxElsHxqnxPLCgg4dBAP7kA4hk7yw19gDh01LwGiOUTltOpMJdWc Ijcg0uz3GM/unS3Ge42ICkCfYIcx7suyLfM23hlTQM/TJtgURtvHBBhvtCm8HNBSSEwKo2PZORbt idAC1QPCXABGYAh1l9Kyk9Zdys0jPdTFonHjovM4wUgfEHvc3z7C2sd7oX8fQWLsZtvG5UqAg5M4 MDeAuQPOLiVuQKKElYomK0GVsFYbN1s3EDTOlI2rUkOi2ErQDEklNZTk26HryuJPijI1+ZT0zn91 OZlbK88opmp6qwcoJ9B5IdQ8iRFSl+YZR5TuDghd4pNcCbGavmefCKGUGkdULwLyijTpPka2MrM4 V+aHbGUpM2Yho3K2/j6gyzn9EtZCZiuVVAhaGxyHrxZj93Lf6UL5+NuV5X4ptl+74+A/Lnb3p8ML j5Pq+TNgjCfW588PLVM0F+a3EicAAAAASUVORK5CYII=" rel="icon" type="image/x-icon" /> )=====");
- Add favicon to the HTML header:
String html=""; html+="<!DOCTYPE HTML><HEAD>"; html+="<TITLE>My App!</TITLE>"; html+=favicon; html+="</HEAD>"; html+="<BODY>"; ...app code... html+="</BODY></HTML>"; //send html to client server.send(200,text/html,html);
Since the header of EVERY page must include the favicon link, I have my header declared statically, and do a string replace.
Here is the result:
![]()
Note:
- Correct icon in upper-left
- No failed call to favicon.ico
Stuff that DID NOT Work
I tried adding a server handler for /favicon.ico and then passing binary file data back to the client. This fails for some uknown reason. I tried multiple file formats and variable declaration formats, and honestly couldn’t get it to work.
The method above is simple and reliable. Unfortunately, it adds 4k to the payload on every page refresh, but that’s not a huge amount of overhead.
Detailed Explanation
Looking at the color-coded code sample above:
| Color | Meaning | |
|---|---|---|
| █ | c++ variable declaration. | |
| █ | c++ syntax for preformatted, multiliine string | |
| █ | HTML syntax | |
| █ | base64-encoded favicon.png |
C declaration:
- “favicon” is declared as a String type.
- “const” is used because the string is immutable (non-variable variable)
- “F( )” function is specific to Arduino. This keeps the string value in non-volatile (PROGMEM) memory rather than copying to RAM.
Preformatted String:
- “R” combined with “=====(” tells C that this is a multiline (pre-formatted) string constant.
- The string constant ends with )=====”
- “=====” can be any 5 character string literal which book-ends the string constant.
- For example, “RABCDEF(String Constant)ABCDEF” works the same as “R=====(String Constant)=====”
- PHP syntax is similar.
HTML syntax:
- <LINK> tag is a general-purpose mechanism for including content from another file.
- “rel” tells the browser what KIND of link, in this case “icon”.
- “type” reiterates the content-type of the linked file.
- in [href=”data:image/x-icon;base64,]
- “href” points to the location of the linked file. Normally, this would be a URL.
- However, “data:” tells the browser that the file’s data is inline.
- “image/x-icon” is the content type.
- “base64” specifies that the file’s data is encoded as base64.
- Everything after the comma “,” is the base64 data
Base64 encoding:
- Three 8-bit bytes of data are encoded as four 6-bit groupings
- Base64 uses ONLY printable characters and is meant to be completely portable across machine types and implementations.
- Uses characters “0-9”, “A-Z”, “a-z”, “/”, “+”, and “=”
- Used for any application where a binary file needs to be embedded in to a readable or printable file, including e-mail, HTML, PKI, and LDAP.
Conclusion
Thank you for reading, and I hope you find this helpful!