diff --git a/src/classes/utils/url.ts b/src/classes/utils/url.ts
new file mode 100644
index 000000000..2539d19c6
--- /dev/null
+++ b/src/classes/utils/url.ts
@@ -0,0 +1,122 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * Parts contained within a url.
+ */
+interface UrlParts {
+
+    /**
+     * Url protocol.
+     */
+    protocol?: string;
+
+    /**
+     * Url domain.
+     */
+    domain?: string;
+
+    /**
+     * Url port.
+     */
+    port?: string;
+
+    /**
+     * Url path.
+     */
+    path?: string;
+
+    /**
+     * Url query.
+     */
+    query?: string;
+
+    /**
+     * Url fragment.
+     */
+    fragment?: string;
+
+}
+
+/**
+ * Singleton with helper functions for urls.
+ */
+export class CoreUrl {
+
+    // Avoid creating singleton instances.
+    private constructor() {}
+
+    /**
+     * Parse parts of a url, using an implicit protocol if it is missing from the url.
+     *
+     * @param url Url.
+     * @return Url parts.
+     */
+    static parse(url: string): UrlParts | null {
+        // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
+        const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
+
+        if (!match) {
+            return null;
+        }
+
+        // Split host into domain and port.
+        const host = match[4] || '';
+        const [domain, port]: string[] = host.indexOf(':') === -1 ? [host] : host.split(':');
+
+        // Prepare parts replacing empty strings with undefined.
+        return {
+            protocol: match[2] || undefined,
+            domain: domain || undefined,
+            port: port || undefined,
+            path: match[5] || undefined,
+            query: match[7] || undefined,
+            fragment: match[9] || undefined,
+        };
+    }
+
+    /**
+     * Guess the Moodle domain from a site url.
+     *
+     * @param url Site url.
+     * @return Guessed Moodle domain.
+     */
+    static guessMoodleDomain(url: string): string | null {
+        // Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make.
+        if (!url.match(/^https?:\/\//)) {
+            url = `https://${url}`;
+        }
+
+        // Match using common suffixes.
+        const knownSuffixes = [
+            '\/my\/?',
+            '\/\\\?redirect=0',
+            '\/index\\\.php',
+            '\/course\/view\\\.php',
+            '\/login\/index\\\.php',
+            '\/mod\/page\/view\\\.php',
+        ];
+        const match = url.match(new RegExp(`^https?:\/\/(.*?)(${knownSuffixes.join('|')})`));
+
+        if (match) {
+            return match[1];
+        }
+
+        // If nothing else worked, parse the domain.
+        const urlParts = CoreUrl.parse(url);
+
+        return urlParts && urlParts.domain ? urlParts.domain : null;
+    }
+
+}
diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts
index 8f0c7e1b6..74596aa20 100644
--- a/src/core/compile/providers/compile.ts
+++ b/src/core/compile/providers/compile.ts
@@ -55,6 +55,7 @@ import { Md5 } from 'ts-md5/dist/md5';
 
 // Import core classes that can be useful for site plugins.
 import { CoreSyncBaseProvider } from '@classes/base-sync';
+import { CoreUrl } from '@classes/utils/url';
 import { CoreCache } from '@classes/cache';
 import { CoreDelegate } from '@classes/delegate';
 import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
@@ -263,6 +264,7 @@ export class CoreCompileProvider {
         instance['moment'] = moment;
         instance['Md5'] = Md5;
         instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
+        instance['CoreUrl'] = CoreUrl;
         instance['CoreCache'] = CoreCache;
         instance['CoreDelegate'] = CoreDelegate;
         instance['CoreContentLinksHandlerBase'] = CoreContentLinksHandlerBase;
diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts
index f9bc3a139..5a231d27c 100644
--- a/src/core/login/pages/site/site.ts
+++ b/src/core/login/pages/site/site.ts
@@ -15,11 +15,12 @@
 import { Component } from '@angular/core';
 import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular';
 import { CoreAppProvider } from '@providers/app';
-import { CoreSitesProvider } from '@providers/sites';
+import { CoreSitesProvider, CoreSiteCheckResponse } from '@providers/sites';
 import { CoreDomUtilsProvider } from '@providers/utils/dom';
 import { CoreConfigConstants } from '../../../../configconstants';
 import { CoreLoginHelperProvider } from '../../providers/helper';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { CoreUrl } from '@classes/utils/url';
 
 /**
  * Page to enter or select the site URL to connect to.
@@ -86,6 +87,8 @@ export class CoreLoginSitePage {
             return;
         }
 
+        url = url.trim();
+
         const modal = this.domUtils.showModalLoading(),
             siteData = this.sitesProvider.getDemoSiteData(url);
 
@@ -111,27 +114,16 @@ export class CoreLoginSitePage {
 
         } else {
             // Not a demo site.
-            this.sitesProvider.checkSite(url).then((result) => {
-                return this.sitesProvider.checkRequiredMinimumVersion(result.config).then(() => {
-                    if (result.warning) {
-                        this.domUtils.showErrorModal(result.warning, true, 4000);
-                    }
+            this.sitesProvider.checkSite(url)
+                .catch((error) => {
+                    // Attempt guessing the domain if the initial check failed
+                    const domain = CoreUrl.guessMoodleDomain(url);
 
-                    if (this.loginHelper.isSSOLoginNeeded(result.code)) {
-                        // SSO. User needs to authenticate in a browser.
-                        this.loginHelper.confirmAndOpenBrowserForSSOLogin(
-                            result.siteUrl, result.code, result.service, result.config && result.config.launchurl);
-                    } else {
-                        this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: result.siteUrl, siteConfig: result.config });
-                    }
-                }).catch(() => {
-                    // Ignore errors.
-                });
-            }, (error) => {
-                this.showLoginIssue(url, error);
-            }).finally(() => {
-                modal.dismiss();
-            });
+                    return domain ? this.sitesProvider.checkSite(domain) : Promise.reject(error);
+                })
+                .then((result) => this.login(result))
+                .catch((error) => this.showLoginIssue(url, error))
+                .finally(() => modal.dismiss());
         }
     }
 
@@ -173,4 +165,30 @@ export class CoreLoginSitePage {
 
         modal.present();
     }
+
+    /**
+     * Process login to a site.
+     *
+     * @param response Response obtained from the site check request.
+     *
+     * @return Promise resolved after logging in.
+     */
+    protected async login(response: CoreSiteCheckResponse): Promise<void> {
+        return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
+            if (response.warning) {
+                this.domUtils.showErrorModal(response.warning, true, 4000);
+            }
+
+            if (this.loginHelper.isSSOLoginNeeded(response.code)) {
+                // SSO. User needs to authenticate in a browser.
+                this.loginHelper.confirmAndOpenBrowserForSSOLogin(
+                    response.siteUrl, response.code, response.service, response.config && response.config.launchurl);
+            } else {
+                this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: response.siteUrl, siteConfig: response.config });
+            }
+        }).catch(() => {
+            // Ignore errors.
+        });
+    }
+
 }