diff --git a/.gitignore b/.gitignore
index 7ae951cf7..0e54c3ff3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -121,4 +121,5 @@ gradle-app.setting
 
 # Other trash
 logs/
-/velocity.toml
\ No newline at end of file
+/velocity.toml
+server-icon.png
\ No newline at end of file
diff --git a/api/src/main/java/com/velocitypowered/api/server/Favicon.java b/api/src/main/java/com/velocitypowered/api/server/Favicon.java
new file mode 100644
index 000000000..ceeaf517c
--- /dev/null
+++ b/api/src/main/java/com/velocitypowered/api/server/Favicon.java
@@ -0,0 +1,88 @@
+package com.velocitypowered.api.server;
+
+import com.google.common.base.Preconditions;
+
+import javax.annotation.Nonnull;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.Objects;
+
+/**
+ * Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be displayed to a remote
+ * client that sends a Server List Ping packet, and is automatically displayed in the Minecraft client.
+ */
+public final class Favicon {
+    private final String base64Url;
+
+    /**
+     * Directly create a favicon using its Base64 URL directly. You are generally better served by the create() series
+     * of functions.
+     * @param base64Url the url for use with this favicon
+     */
+    public Favicon(@Nonnull String base64Url) {
+        this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url");
+    }
+
+    /**
+     * Returns the Base64-encoded URI for this image.
+     * @return a URL representing this favicon
+     */
+    public String getBase64Url() {
+        return base64Url;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Favicon favicon = (Favicon) o;
+        return Objects.equals(base64Url, favicon.base64Url);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(base64Url);
+    }
+
+    @Override
+    public String toString() {
+        return "Favicon{" +
+                "base64Url='" + base64Url + '\'' +
+                '}';
+    }
+
+    /**
+     * Creates a new {@code Favicon} from the specified {@code image}.
+     * @param image the image to use for the favicon
+     * @return the created {@link Favicon} instance
+     */
+    public static Favicon create(@Nonnull BufferedImage image) {
+        Preconditions.checkNotNull(image, "image");
+        Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" +
+                " 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight());
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        try {
+            ImageIO.write(image, "PNG", os);
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+        return new Favicon("data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()));
+    }
+
+    /**
+     * Creates a new {@code Favicon} by reading the image from the specified {@code path}.
+     * @param path the path to the image to create a favicon for
+     * @return the created {@link Favicon} instance
+     */
+    public static Favicon create(@Nonnull Path path) throws IOException {
+        try (InputStream stream = Files.newInputStream(path)) {
+            return create(ImageIO.read(stream));
+        }
+    }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java
index 6efd6a6c7..e2ee28950 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java
@@ -3,6 +3,12 @@ package com.velocitypowered.proxy;
 import com.velocitypowered.proxy.console.VelocityConsole;
 
 public class Velocity {
+    static {
+        // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
+        // Force AWT to work with its head chopped off.
+        System.setProperty("java.awt.headless", "true");
+    }
+
     public static void main(String... args) {
         final VelocityServer server = VelocityServer.getServer();
         server.start();
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
index 3cff9b6bd..e548d54c3 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
@@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder;
 import com.velocitypowered.api.command.CommandInvoker;
 import com.velocitypowered.api.proxy.Player;
 import com.velocitypowered.api.proxy.ProxyServer;
+import com.velocitypowered.api.server.Favicon;
 import com.velocitypowered.natives.util.Natives;
 import com.velocitypowered.network.ConnectionManager;
 import com.velocitypowered.proxy.command.ServerCommand;
@@ -17,6 +18,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
 import com.velocitypowered.proxy.connection.http.NettyHttpClient;
 import com.velocitypowered.api.server.ServerInfo;
 import com.velocitypowered.proxy.command.CommandManager;
+import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
 import com.velocitypowered.proxy.util.AddressUtil;
 import com.velocitypowered.proxy.util.EncryptionUtils;
 import com.velocitypowered.proxy.util.ServerMap;
@@ -44,6 +46,7 @@ public class VelocityServer implements ProxyServer {
     private static final VelocityServer INSTANCE = new VelocityServer();
     public static final Gson GSON = new GsonBuilder()
             .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
+            .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer())
             .create();
 
     private final ConnectionManager cm = new ConnectionManager();
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
index 10f697903..cd5f5536c 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
@@ -2,6 +2,7 @@ package com.velocitypowered.proxy.config;
 
 import com.google.common.collect.ImmutableMap;
 import com.moandjiezana.toml.Toml;
+import com.velocitypowered.api.server.Favicon;
 import com.velocitypowered.proxy.util.AddressUtil;
 import com.velocitypowered.api.util.LegacyChatColorUtils;
 import net.kyori.text.Component;
@@ -15,6 +16,7 @@ import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -36,6 +38,7 @@ public class VelocityConfiguration {
     private final int queryPort;
 
     private Component motdAsComponent;
+    private Favicon favicon;
 
     private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
                                   IPForwardingMode ipForwardingMode, Map<String, String> servers,
@@ -124,9 +127,22 @@ public class VelocityConfiguration {
             logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance.");
         }
 
+        loadFavicon();
+
         return valid;
     }
 
+    private void loadFavicon() {
+        Path faviconPath = Paths.get("server-icon.png");
+        if (Files.exists(faviconPath)) {
+            try {
+                this.favicon = Favicon.create(faviconPath);
+            } catch (Exception e) {
+                logger.info("Unable to load your server-icon.png, continuing without it.", e);
+            }
+        }
+    }
+
     public InetSocketAddress getBind() {
         return AddressUtil.parseAddress(bind);
     }
@@ -182,6 +198,14 @@ public class VelocityConfiguration {
         return compressionLevel;
     }
 
+    public Favicon getFavicon() {
+        return favicon;
+    }
+
+    public static Logger getLogger() {
+        return logger;
+    }
+
     @Override
     public String toString() {
         return "VelocityConfiguration{" +
@@ -197,6 +221,7 @@ public class VelocityConfiguration {
                 ", queryEnabled=" + queryEnabled +
                 ", queryPort=" + queryPort +
                 ", motdAsComponent=" + motdAsComponent +
+                ", favicon=" + favicon +
                 '}';
     }
 
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java
index e75eafebd..7d5f886bd 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java
@@ -41,7 +41,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
                 new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
                 new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()),
                 configuration.getMotdComponent(),
-                null
+                configuration.getFavicon()
         );
         StatusResponse response = new StatusResponse();
         response.setStatus(VelocityServer.GSON.toJson(ping));
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java b/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java
index 72494eaf1..a6cb2a746 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/data/ServerPing.java
@@ -1,14 +1,15 @@
 package com.velocitypowered.proxy.data;
 
+import com.velocitypowered.api.server.Favicon;
 import net.kyori.text.Component;
 
 public class ServerPing {
     private final Version version;
     private final Players players;
     private final Component description;
-    private final String favicon;
+    private final Favicon favicon;
 
-    public ServerPing(Version version, Players players, Component description, String favicon) {
+    public ServerPing(Version version, Players players, Component description, Favicon favicon) {
         this.version = version;
         this.players = players;
         this.description = description;
@@ -27,7 +28,7 @@ public class ServerPing {
         return description;
     }
 
-    public String getFavicon() {
+    public Favicon getFavicon() {
         return favicon;
     }
 
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java
new file mode 100644
index 000000000..7243e8b06
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java
@@ -0,0 +1,18 @@
+package com.velocitypowered.proxy.protocol.util;
+
+import com.google.gson.*;
+import com.velocitypowered.api.server.Favicon;
+
+import java.lang.reflect.Type;
+
+public class FaviconSerializer implements JsonSerializer<Favicon>, JsonDeserializer<Favicon> {
+    @Override
+    public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+        return new Favicon(json.getAsString());
+    }
+
+    @Override
+    public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(src.getBase64Url());
+    }
+}