diff --git a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
index 2c77a2ef885ad61cb39e15b078447f74a4a5ea89..13858c3a6c2d43dc428712d96d7db9b0910898c9 100644
--- a/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
+++ b/apps/jetty/java/src/net/i2p/jetty/JettyStart.java
@@ -65,7 +65,6 @@ public class JettyStart implements ClientApp {
     private static final String GZIP_DIR = "eepsite-jetty9.3";
     private static final String GZIP_CONFIG = "jetty-gzip.xml";
     private static final String MIN_GZIP_HANDLER_VER = "9.3";
-
     /**
      *  All args must be XML file names.
      *  Does not support any of the other argument types from org.mortbay.start.Main.
@@ -179,6 +178,10 @@ public class JettyStart implements ClientApp {
         public void run() {
             for (LifeCycle lc : _jettys) {
                 if (!lc.isRunning()) {
+                    if (lc instanceof Server) {
+                        Server server = (Server) lc;
+                        server.insertHandler(new XI2PLocationFilter());
+                    }
                     try {
                         lc.start();
                         if (_context != null) {
diff --git a/apps/jetty/java/src/net/i2p/servlet/filters/XI2PLocationFilter.java b/apps/jetty/java/src/net/i2p/servlet/filters/XI2PLocationFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..05441e28d69b73fc7239075b973894b558aeb40d
--- /dev/null
+++ b/apps/jetty/java/src/net/i2p/servlet/filters/XI2PLocationFilter.java
@@ -0,0 +1,188 @@
+package net.i2p.jetty;
+
+import java.io.IOException;
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+import net.i2p.I2PAppContext;
+import net.i2p.I2PException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
+import net.i2p.data.PrivateKeyFile;
+
+
+import net.i2p.util.Log;
+
+/**
+ * Adds a header, X-I2P-Location, to requests when they do **not** come in on an I2P hostname.
+ * This header contains a URL that looks like: [scheme://][i2phostname.i2p][/path][?query]
+ * and expresses the I2P-Equivalent URL of the clearnet query. Clients can use this to prompt
+ * users to switch from a non-I2P host to an I2P host or to redirect them automatically. It
+ * automatically enabled on the default I2P site located on port 7658 by default.
+ *
+ *  @since 0.9.51
+ */
+public class XI2PLocationFilter extends HandlerWrapper {
+    private String X_I2P_Location = null;
+    private long lastFailure = -1;
+    private static final long failTimeout = 600000;
+    private static final String encodeUTF = StandardCharsets.UTF_8.toString();
+    private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(XI2PLocationFilter.class);
+
+
+    private synchronized void setLocation(String xi2plocation) {
+        if (_log.shouldInfo())
+            _log.info("Checking X-I2P-Location header prefix" + xi2plocation);
+        if (X_I2P_Location != null)
+            return ;
+        if (xi2plocation == null)
+            return ;
+        if (xi2plocation.equals(""))
+            return ;
+        X_I2P_Location = xi2plocation;
+        if (_log.shouldInfo())
+            _log.info("Caching X-I2P-Location header prefix" + X_I2P_Location);
+    }
+
+    private synchronized boolean shouldRecheck(){
+        boolean settable = (X_I2P_Location == null);
+        if (!settable) return settable;
+        if (lastFailure == -1) {
+            lastFailure = System.currentTimeMillis();
+            if (_log.shouldDebug())
+                _log.debug("New instance, attempting to set X-I2P-Location header for the first time");
+            return settable;
+        }
+        if ((System.currentTimeMillis() - lastFailure) > failTimeout){
+            lastFailure = System.currentTimeMillis();
+            if (_log.shouldDebug())
+                _log.debug("More than ten minutes since failing attempt to re-check X-I2P-Location header");
+            return settable;
+        }
+        if (_log.shouldDebug())
+            _log.debug("Not attempting to re-check X-I2P-Location header");
+        return false;
+    }
+
+    private synchronized String getXI2PLocation(String host, String port) {
+        File configDir = I2PAppContext.getGlobalContext().getConfigDir();
+        File tunnelConfig = new File(configDir, "i2ptunnel.config");
+        boolean isSingleFile = tunnelConfig.exists();
+        if (!isSingleFile) {
+            File tunnelConfigD = new File(configDir, "i2ptunnel.config.d");
+            File[] configFiles = tunnelConfigD.listFiles(new net.i2p.util.FileSuffixFilter(".config"));
+            if (configFiles == null)
+                return null;
+            for (int fnum=0; fnum < configFiles.length; fnum++) {
+                Properties tunnelProps = new Properties();
+                try {
+                    DataHelper.loadProps(tunnelProps, configFiles[fnum]);
+                    String targetHost = tunnelProps.getProperty("targetHost");
+                    boolean hostmatch = (host.equals(targetHost) || "0.0.0.0".equals(targetHost) || "::".equals(targetHost));
+                    if ( hostmatch && port.equals(tunnelProps.getProperty("targetPort")) ) {
+                        String sh = tunnelProps.getProperty("spoofedHost");
+                        if (sh != null) {
+                            if (sh.endsWith(".i2p"))
+                                return sh;
+                        }
+                        String kf = tunnelProps.getProperty("privKeyFile");
+                        if (kf != null) {
+                            File keyFile = new File(kf);
+                            if (!keyFile.isAbsolute())
+                                keyFile = new File(configDir, kf);
+                            if (keyFile.exists()) {
+                                PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
+                                try {
+                                    Destination rv = pkf.getDestination();
+                                    if (rv != null)
+                                        return rv.toBase32();
+                                } catch (I2PException e) {
+                                    if (_log.shouldWarn())
+                                        _log.warn("I2PException Unable to set X-I2P-Location, keys arent ready. This is probably safe to ignore and will go away after the first run." + e);
+                                    return null;
+                                } catch (IOException e) {
+                                    if (_log.shouldWarn())
+                                        _log.warn("IOE Unable to set X-I2P-Location, location is uninitialized due file not found. This probably means the keys aren't ready. This is probably safe to ignore." + e);
+                                    return null;
+                                }
+                            }
+                        }
+                        if (_log.shouldWarn())
+                            _log.warn("Unable to set X-I2P-Location, location is target not found in any I2PTunnel config file. This should never happen.");
+                        return null;
+                    }
+                } catch (IOException ioe) {
+                    if (_log.shouldWarn())
+                        _log.warn("IOE Unable to set X-I2P-Location, location is uninitialized. This is probably safe to ignore. location='" + ioe + "'");
+                    return null;
+                }
+            }
+        } else {
+            // don't bother
+        }
+        return null;
+    }
+
+    private synchronized String headerContents(final HttpServletRequest httpRequest) {
+        if (X_I2P_Location != null) {
+            String scheme = httpRequest.getScheme();
+            if (scheme == null)
+                 scheme = "";
+            String path = httpRequest.getPathInfo();
+            if (path == null)
+                path = "";
+            String query = httpRequest.getQueryString();
+            if (query == null)
+                query = "";
+            try {
+                if (query.equals("")) {
+                    URI uri = new URI(scheme, X_I2P_Location, path, null);
+                    String encodedURL = uri.toASCIIString();
+                    return encodedURL;
+                } else {
+                    URI uri = new URI(scheme, X_I2P_Location, path, query, null);
+                    String encodedURL = uri.toASCIIString();
+                    return encodedURL;
+                }
+            }catch(URISyntaxException use){
+                return null;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void handle(final String target, final Request request, final HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+    throws IOException, ServletException {
+        final String hashHeader = httpRequest.getHeader("X-I2P-DestHash");
+
+        if (hashHeader == null) {
+            if (shouldRecheck()) {
+                String xi2plocation = getXI2PLocation(request.getLocalAddr(), String.valueOf(request.getLocalPort()));
+                if (_log.shouldInfo())
+                   _log.info("Checking X-I2P-Location header IP " + request.getLocalAddr() + " port " + request.getLocalPort() + " prefix " + xi2plocation);
+                setLocation(xi2plocation);
+            }
+            String headerURL = headerContents(httpRequest);
+            if (headerURL != null) {
+                if (_log.shouldInfo())
+                    _log.info("Checking X-I2P-Location header" + headerURL);
+                httpResponse.addHeader("X-I2P-Location", headerURL);
+            }
+        }
+
+        _handler.handle(target, request, httpRequest, httpResponse);
+    }
+}