diff --git a/src/metabase/util.clj b/src/metabase/util.clj
index d933b3e449ef902529dd318c9d6b5563fd5a1617..d4158b9ccab8cb07b9bc33fe162541ee8e9a3a63 100644
--- a/src/metabase/util.clj
+++ b/src/metabase/util.clj
@@ -307,6 +307,13 @@
       [default args]))
 
 
+(defmacro ignore-exceptions
+  "Simple macro which wraps the given expression in a try/catch block and ignores the exception if caught."
+  {:style/indent 0}
+  [& body]
+  `(try ~@body (catch Throwable ~'_)))
+
+
 ;; TODO - rename to `email?`
 (defn is-email?
   "Is STRING a valid email address?"
@@ -317,15 +324,18 @@
 
 ;; TODO - rename to `url?`
 (defn is-url?
-  "Is STRING a valid HTTP/HTTPS URL?"
+  "Is STRING a valid HTTP/HTTPS URL? (This only handles `localhost` and domains like `metabase.com`; URLs containing IP addresses will return `false`.)"
   ^Boolean [^String s]
-  (boolean (when s
-             (when-let [^java.net.URL url (try (java.net.URL. s)
-                                               (catch java.net.MalformedURLException _ ; TODO - use ignore-exceptions
-                                                 nil))]
-               (when (and (.getProtocol url) (.getAuthority url))
-                 (and (re-matches #"^https?$" (.getProtocol url))           ; these are both automatically downcased
-                      (re-matches #"^.+\..{2,}$" (.getAuthority url)))))))) ; this is the part like 'google.com'. Make sure it contains at least one period and 2+ letter TLD
+  (boolean (when (seq s)
+             (when-let [^java.net.URL url (ignore-exceptions (java.net.URL. s))]
+               ;; these are both automatically downcased
+               (let [protocol (.getProtocol url)
+                     host     (.getHost url)]
+                 (and protocol
+                      host
+                      (re-matches #"^https?$" protocol)
+                      (or (re-matches #"^.+\..{2,}$" host) ; 2+ letter TLD
+                          (= host "localhost"))))))))
 
 
 ;; TODO - This should be made into a separate `sequence-of-maps?` function and a `maybe?` function
@@ -525,12 +535,6 @@
                                                      (last args)
                                                      [(last args)]))))
 
-(defmacro ignore-exceptions
-  "Simple macro which wraps the given expression in a try/catch block and ignores the exception if caught."
-  {:style/indent 0}
-  [& body]
-  `(try ~@body (catch Throwable ~'_)))
-
 (defn deref-with-timeout
   "Call `deref` on a FUTURE and throw an exception if it takes more than TIMEOUT-MS."
   [futur timeout-ms]
diff --git a/test/metabase/util_test.clj b/test/metabase/util_test.clj
index cc64814e045f864415883bb303c5dbd835a68b93..7403b3a5c45956632e3330ed51eaac54b32eeddc 100644
--- a/test/metabase/util_test.clj
+++ b/test/metabase/util_test.clj
@@ -67,6 +67,10 @@
 (expect true (is-url? "https://amazon.co.uk"))
 (expect true (is-url? "http://google.com?q=my-query&etc"))
 (expect true (is-url? "http://www.cool.com"))
+(expect true (is-url? "http://localhost/"))
+(expect true (is-url? "http://localhost:3000"))
+(expect true (is-url? "http://www.cool.com:3000"))
+(expect true (is-url? "http://localhost:3000/auth/reset_password/144_f98987de-53ca-4335-81da-31bb0de8ea2b#new"))
 (expect false (is-url? "google.com"))                      ; missing protocol
 (expect false (is-url? "ftp://metabase.com"))              ; protocol isn't HTTP/HTTPS
 (expect false (is-url? "http://metabasecom"))              ; no period / TLD