diff --git a/__tests__/package-resolver.js b/__tests__/package-resolver.js
index 9e5691dc0d..05097ceb69 100644
--- a/__tests__/package-resolver.js
+++ b/__tests__/package-resolver.js
@@ -12,6 +12,7 @@ import * as fs from '../src/util/fs.js';
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
 
 const path = require('path');
+const isCI = require('is-ci');
 
 function addTest(pattern, registry = 'npm') {
   // TODO renable these test.concurrent
@@ -35,6 +36,7 @@ function addTest(pattern, registry = 'npm') {
   });
 }
 
+// Public deps
 addTest('https://github.com/npm-ml/re'); // git url with no .git
 addTest('https://bitbucket.org/hgarcia/node-bitbucket-api.git'); // hosted git url
 addTest('https://github.com/PolymerElements/font-roboto/archive/2fd5c7bd715a24fb5b250298a140a3ba1b71fe46.tar.gz'); // tarball
@@ -51,3 +53,12 @@ addTest('react-native'); // npm
 addTest('ember-cli'); // npm
 addTest('npm:gulp'); // npm
 addTest('@polymer/iron-icon'); // npm scoped package
+
+// Private deps
+
+// Only the yarn CI tools have access to this private deps. If you want to test this locally,
+// remove the if condition and change the urls to match your private deps.
+if (isCI) {
+  addTest('yarnpkg/private-dep#c6cf811'); // private github shortcut
+  addTest('github:yarnpkg/private-dep#c6cf811'); // private github shortcut, with provider
+}
diff --git a/__tests__/resolvers/exotics/bitbucket-resolver.js b/__tests__/resolvers/exotics/bitbucket-resolver.js
index 7c1bb5c915..09aafeafda 100644
--- a/__tests__/resolvers/exotics/bitbucket-resolver.js
+++ b/__tests__/resolvers/exotics/bitbucket-resolver.js
@@ -35,14 +35,25 @@ test('getGitHTTPUrl should return the correct git bitbucket url', () => {
   expect(BitBucketResolver.getGitHTTPUrl(fragment)).toBe(expected);
 });
 
-test('getGitHTTPUrl should return the correct git bitbucket SSH url', () => {
+test('getGitSSH should return the correct git bitbucket SSH url', () => {
   const fragment: ExplodedFragment = {
     user: 'foo',
     repo: 'bar',
     hash: '',
   };
 
-  const expected =  'git@bitbucket.org:' + fragment.user + '/' + fragment.repo + '.git';
+  const expected =  `git@bitbucket.org:${fragment.user}/${fragment.repo}.git`;
+  expect(BitBucketResolver.getGitSSH(fragment)).toBe(expected);
+});
+
+test('getGitSSHUrl should return the correct git bitbucket SSH url', () => {
+  const fragment: ExplodedFragment = {
+    user: 'foo',
+    repo: 'bar',
+    hash: '',
+  };
+
+  const expected =  `git+ssh://git@bitbucket.org/${fragment.user}/${fragment.repo}.git`;
   expect(BitBucketResolver.getGitSSHUrl(fragment)).toBe(expected);
 });
 
diff --git a/package.json b/package.json
index b275b4ec0c..e3a5ec043b 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
     "ini": "^1.3.4",
     "invariant": "^2.2.0",
     "is-builtin-module": "^1.0.0",
+    "is-ci": "^1.0.9",
     "leven": "^2.0.0",
     "loud-rejection": "^1.2.0",
     "minimatch": "^3.0.3",
diff --git a/src/resolvers/exotics/bitbucket-resolver.js b/src/resolvers/exotics/bitbucket-resolver.js
index 39e1beed32..4805058e21 100644
--- a/src/resolvers/exotics/bitbucket-resolver.js
+++ b/src/resolvers/exotics/bitbucket-resolver.js
@@ -16,6 +16,10 @@ export default class BitbucketResolver extends HostedGitResolver {
   }
 
   static getGitSSHUrl(parts: ExplodedFragment): string {
+    return `git+ssh://git@bitbucket.org/${ parts.user }/${ parts.repo }.git`;
+  }
+
+  static getGitSSH(parts: ExplodedFragment): string {
     return `git@bitbucket.org:${parts.user}/${parts.repo}.git`;
   }
 
diff --git a/src/resolvers/exotics/github-resolver.js b/src/resolvers/exotics/github-resolver.js
index 31ded8c6a4..9a7ea5d002 100644
--- a/src/resolvers/exotics/github-resolver.js
+++ b/src/resolvers/exotics/github-resolver.js
@@ -26,6 +26,11 @@ export default class GitHubResolver extends HostedGitResolver {
   }
 
   static getGitSSHUrl(parts: ExplodedFragment): string {
+    return `git+ssh://git@github.com/${ parts.user }/${ parts.repo }.git` +
+      `${parts.hash ? '#' + decodeURIComponent(parts.hash) : ''}`;
+  }
+
+  static getGitSSH(parts: ExplodedFragment): string {
     return `git@${this.hostname}:${parts.user}/${parts.repo}.git` +
       `${parts.hash ? '#' + decodeURIComponent(parts.hash) : ''}`;
   }
diff --git a/src/resolvers/exotics/gitlab-resolver.js b/src/resolvers/exotics/gitlab-resolver.js
index dd94a08fcf..9aa25a01a4 100644
--- a/src/resolvers/exotics/gitlab-resolver.js
+++ b/src/resolvers/exotics/gitlab-resolver.js
@@ -16,6 +16,10 @@ export default class GitLabResolver extends HostedGitResolver {
   }
 
   static getGitSSHUrl(parts: ExplodedFragment): string {
+    return `git+ssh://git@gitlab.com/${ parts.user }/${ parts.repo }.git`;
+  }
+
+  static getGitSSH(parts: ExplodedFragment): string {
     return `git@gitlab.com:${parts.user}/${parts.repo}.git`;
   }
 
diff --git a/src/resolvers/exotics/hosted-git-resolver.js b/src/resolvers/exotics/hosted-git-resolver.js
index ba5f7ae56a..5e810798a8 100644
--- a/src/resolvers/exotics/hosted-git-resolver.js
+++ b/src/resolvers/exotics/hosted-git-resolver.js
@@ -71,6 +71,11 @@ export default class HostedGitResolver extends ExoticResolver {
     throw new Error('Not implemented');
   }
 
+  static getGitSSH(exploded: ExplodedFragment): string {
+    exploded;
+    throw new Error('Not implemented');
+  }
+
   static getHTTPFileUrl(exploded: ExplodedFragment, filename: string, commit: string) {
     exploded;
     filename;
@@ -169,14 +174,15 @@ export default class HostedGitResolver extends ExoticResolver {
   }
 
   async resolve(): Promise<Manifest> {
-    const httpUrl = this.constructor.getGitHTTPUrl(this.exploded);
-    const sshUrl = this.constructor.getGitSSHUrl(this.exploded);
+    const gitHTTPUrl = this.constructor.getGitHTTPUrl(this.exploded);
+    const gitSSHUrl = this.constructor.getGitSSHUrl(this.exploded);
+    const gitSSH = this.constructor.getGitSSH(this.exploded);
 
     // If we can access the files over HTTP then we should as it's MUCH faster than git
     // archive and tarball unarchiving. The HTTP API is only available for public repos
     // though.
-    if (await this.hasHTTPCapability(httpUrl)) {
-      return await this.resolveOverHTTP(httpUrl);
+    if (await this.hasHTTPCapability(gitHTTPUrl)) {
+      return await this.resolveOverHTTP(gitHTTPUrl);
     }
 
     // If the url is accessible over git archive then we should immediately delegate to
@@ -185,13 +191,13 @@ export default class HostedGitResolver extends ExoticResolver {
     // NOTE: Here we use a different url than when we delegate to the git resolver later on.
     // This is because `git archive` requires access over ssh and github only allows that
     // if you have write permissions
-    if (await Git.hasArchiveCapability(sshUrl)) {
-      const archiveClient = new Git(this.config, sshUrl, this.hash);
+    if (await Git.hasArchiveCapability(gitSSH)) {
+      const archiveClient = new Git(this.config, gitSSH, this.hash);
       const commit = await archiveClient.initRemote();
-      return await this.fork(GitResolver, true, `${sshUrl}#${commit}`);
+      return await this.fork(GitResolver, true, `${gitSSH}#${commit}`);
     }
 
     // fallback to the plain git resolver
-    return await this.fork(GitResolver, true, sshUrl);
+    return await this.fork(GitResolver, true, gitSSHUrl);
   }
 }
diff --git a/yarn.lock b/yarn.lock
index aeb7aa32dc..c21a32f47b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2671,7 +2671,7 @@ is-builtin-module@^1.0.0:
   dependencies:
     builtin-modules "^1.0.0"
 
-is-ci@^1.0.9:
+is-ci, is-ci@^1.0.9:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.9.tgz#de2c5ffe49ab3237fda38c47c8a3bbfd55bbcca7"