diff --git a/core/base-service/legacy-request-handler.spec.js b/core/base-service/legacy-request-handler.spec.js
index b47e72d36462a1c069b79f48034f08eeb0055e0c..ecb49abe2da32f5629c6584c02483cee71a9b166 100644
--- a/core/base-service/legacy-request-handler.spec.js
+++ b/core/base-service/legacy-request-handler.spec.js
@@ -1,7 +1,8 @@
 'use strict'
 
 const { expect } = require('chai')
-const fetch = require('node-fetch')
+// https://github.com/nock/nock/issues/1523
+const got = require('got').extend({ retry: 0 })
 const nock = require('nock')
 const portfinder = require('portfinder')
 const Camp = require('camp')
@@ -13,8 +14,8 @@ const {
 } = require('./legacy-request-handler')
 
 async function performTwoRequests(baseUrl, first, second) {
-  expect((await fetch(`${baseUrl}${first}`)).ok).to.be.true
-  expect((await fetch(`${baseUrl}${second}`)).ok).to.be.true
+  expect((await got(`${baseUrl}${first}`)).statusCode).to.equal(200)
+  expect((await got(`${baseUrl}${second}`)).statusCode).to.equal(200)
 }
 
 function fakeHandler(queryParams, match, sendBadge, request) {
@@ -101,9 +102,11 @@ describe('The request handler', function() {
     })
 
     it('should return the expected response', async function() {
-      const res = await fetch(`${baseUrl}/testing/123.json`)
-      expect(res.ok).to.be.true
-      expect(await res.json()).to.deep.equal({
+      const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
+        json: true,
+      })
+      expect(statusCode).to.equal(200)
+      expect(body).to.deep.equal({
         name: 'testing',
         value: '123',
         label: 'testing',
@@ -123,9 +126,11 @@ describe('The request handler', function() {
     })
 
     it('should return the expected response', async function() {
-      const res = await fetch(`${baseUrl}/testing/123.json`)
-      expect(res.ok).to.be.true
-      expect(await res.json()).to.deep.equal({
+      const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
+        json: true,
+      })
+      expect(statusCode).to.equal(200)
+      expect(body).to.deep.equal({
         name: 'testing',
         value: '123',
         label: 'testing',
@@ -152,9 +157,11 @@ describe('The request handler', function() {
         .get('/foo/bar')
         .once()
         .reply(200, 'x'.repeat(100))
-      const res = await fetch(`${baseUrl}/testing/123.json`)
-      expect(res.ok).to.be.true
-      expect(await res.json()).to.deep.equal({
+      const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
+        json: true,
+      })
+      expect(statusCode).to.equal(200)
+      expect(body).to.deep.equal({
         name: 'testing',
         value: '123',
         label: 'testing',
@@ -169,9 +176,11 @@ describe('The request handler', function() {
         .get('/foo/bar')
         .once()
         .reply(200, 'x'.repeat(101))
-      const res = await fetch(`${baseUrl}/testing/123.json`)
-      expect(res.ok).to.be.true
-      expect(await res.json()).to.deep.equal({
+      const { statusCode, body } = await got(`${baseUrl}/testing/123.json`, {
+        json: true,
+      })
+      expect(statusCode).to.equal(200)
+      expect(body).to.deep.equal({
         name: 'testing',
         value: 'Maximum response size exceeded',
         label: 'testing',
@@ -241,26 +250,26 @@ describe('The request handler', function() {
 
       it('should set the expires header to current time + defaultCacheLengthSeconds', async function() {
         register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
-        const res = await fetch(`${baseUrl}/testing/123.json`)
+        const { headers } = await got(`${baseUrl}/testing/123.json`)
         const expectedExpiry = new Date(
-          +new Date(res.headers.get('date')) + 900000
+          +new Date(headers.date) + 900000
         ).toGMTString()
-        expect(res.headers.get('expires')).to.equal(expectedExpiry)
-        expect(res.headers.get('cache-control')).to.equal('max-age=900')
+        expect(headers.expires).to.equal(expectedExpiry)
+        expect(headers['cache-control']).to.equal('max-age=900')
       })
 
       it('should set the expected cache headers on cached responses', async function() {
         register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
 
         // Make first request.
-        await fetch(`${baseUrl}/testing/123.json`)
+        await got(`${baseUrl}/testing/123.json`)
 
-        const res = await fetch(`${baseUrl}/testing/123.json`)
+        const { headers } = await got(`${baseUrl}/testing/123.json`)
         const expectedExpiry = new Date(
-          +new Date(res.headers.get('date')) + 900000
+          +new Date(headers.date) + 900000
         ).toGMTString()
-        expect(res.headers.get('expires')).to.equal(expectedExpiry)
-        expect(res.headers.get('cache-control')).to.equal('max-age=900')
+        expect(headers.expires).to.equal(expectedExpiry)
+        expect(headers['cache-control']).to.equal('max-age=900')
       })
 
       it('should let live service data override the default cache headers with longer value', async function() {
@@ -280,8 +289,8 @@ describe('The request handler', function() {
           )
         )
 
-        const res = await fetch(`${baseUrl}/testing/123.json`)
-        expect(res.headers.get('cache-control')).to.equal('max-age=400')
+        const { headers } = await got(`${baseUrl}/testing/123.json`)
+        expect(headers['cache-control']).to.equal('max-age=400')
       })
 
       it('should not let live service data override the default cache headers with shorter value', async function() {
@@ -301,35 +310,39 @@ describe('The request handler', function() {
           )
         )
 
-        const res = await fetch(`${baseUrl}/testing/123.json`)
-        expect(res.headers.get('cache-control')).to.equal('max-age=300')
+        const { headers } = await got(`${baseUrl}/testing/123.json`)
+        expect(headers['cache-control']).to.equal('max-age=300')
       })
 
       it('should set the expires header to current time + cacheSeconds', async function() {
         register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
-        const res = await fetch(`${baseUrl}/testing/123.json?cacheSeconds=3600`)
+        const { headers } = await got(
+          `${baseUrl}/testing/123.json?cacheSeconds=3600`
+        )
         const expectedExpiry = new Date(
-          +new Date(res.headers.get('date')) + 3600000
+          +new Date(headers.date) + 3600000
         ).toGMTString()
-        expect(res.headers.get('expires')).to.equal(expectedExpiry)
-        expect(res.headers.get('cache-control')).to.equal('max-age=3600')
+        expect(headers.expires).to.equal(expectedExpiry)
+        expect(headers['cache-control']).to.equal('max-age=3600')
       })
 
       it('should ignore cacheSeconds when shorter than defaultCacheLengthSeconds', async function() {
         register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 600 } })
-        const res = await fetch(`${baseUrl}/testing/123.json?cacheSeconds=300`)
+        const { headers } = await got(
+          `${baseUrl}/testing/123.json?cacheSeconds=300`
+        )
         const expectedExpiry = new Date(
-          +new Date(res.headers.get('date')) + 600000
+          +new Date(headers.date) + 600000
         ).toGMTString()
-        expect(res.headers.get('expires')).to.equal(expectedExpiry)
-        expect(res.headers.get('cache-control')).to.equal('max-age=600')
+        expect(headers.expires).to.equal(expectedExpiry)
+        expect(headers['cache-control']).to.equal('max-age=600')
       })
 
       it('should set Cache-Control: no-cache, no-store, must-revalidate if cache seconds is 0', async function() {
         register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
-        const res = await fetch(`${baseUrl}/testing/123.json`)
-        expect(res.headers.get('expires')).to.equal(res.headers.get('date'))
-        expect(res.headers.get('cache-control')).to.equal(
+        const { headers } = await got(`${baseUrl}/testing/123.json`)
+        expect(headers.expires).to.equal(headers.date)
+        expect(headers['cache-control']).to.equal(
           'no-cache, no-store, must-revalidate'
         )
       })
@@ -340,17 +353,11 @@ describe('The request handler', function() {
         })
         const expectedCacheKey = '/testing/123.json?color=123&label=foo'
         it('should match expected and use canonical order - 1', async function() {
-          const res = await fetch(
-            `${baseUrl}/testing/123.json?color=123&label=foo`
-          )
-          expect(res.ok).to.be.true
+          await got(`${baseUrl}/testing/123.json?color=123&label=foo`)
           expect(_requestCache.cache).to.have.keys(expectedCacheKey)
         })
         it('should match expected and use canonical order - 2', async function() {
-          const res = await fetch(
-            `${baseUrl}/testing/123.json?label=foo&color=123`
-          )
-          expect(res.ok).to.be.true
+          await got(`${baseUrl}/testing/123.json?label=foo&color=123`)
           expect(_requestCache.cache).to.have.keys(expectedCacheKey)
         })
       })
diff --git a/core/server/prometheus-metrics.spec.js b/core/server/prometheus-metrics.spec.js
index 32cdf006228bab2f10eac8b0f478d594c8007599..31bab1a45b793c0df890adb89ac830e58c611644 100644
--- a/core/server/prometheus-metrics.spec.js
+++ b/core/server/prometheus-metrics.spec.js
@@ -1,9 +1,10 @@
 'use strict'
 
 const { expect } = require('chai')
+// https://github.com/nock/nock/issues/1523
+const got = require('got').extend({ retry: 0 })
 const Camp = require('camp')
 const portfinder = require('portfinder')
-const fetch = require('node-fetch')
 const Metrics = require('./prometheus-metrics')
 
 describe('Prometheus metrics route', function() {
@@ -28,9 +29,9 @@ describe('Prometheus metrics route', function() {
   it('returns metrics', async function() {
     new Metrics({ enabled: true }).initialize(camp)
 
-    const res = await fetch(`${baseUrl}/metrics`)
+    const { statusCode, body } = await got(`${baseUrl}/metrics`)
 
-    expect(res.status).to.be.equal(200)
-    expect(await res.text()).to.contains('nodejs_version_info')
+    expect(statusCode).to.be.equal(200)
+    expect(body).to.contain('nodejs_version_info')
   })
 })
diff --git a/core/server/server.spec.js b/core/server/server.spec.js
index 8f29f2e593a7aac6a0c1a18a6fba5638708cd4f1..3bae9a8c4a4d523de1daafa908a03c5bc09623b1 100644
--- a/core/server/server.spec.js
+++ b/core/server/server.spec.js
@@ -3,8 +3,8 @@
 const fs = require('fs')
 const path = require('path')
 const { expect } = require('chai')
-const fetch = require('node-fetch')
-const got = require('got')
+// https://github.com/nock/nock/issues/1523
+const got = require('got').extend({ retry: 0 })
 const isPng = require('is-png')
 const isSvg = require('is-svg')
 const sinon = require('sinon')
@@ -30,77 +30,93 @@ describe('The server', function() {
   })
 
   it('should produce colorscheme badges', async function() {
-    const res = await fetch(`${baseUrl}:fruit-apple-green.svg`)
-    expect(res.ok).to.be.true
-    expect(await res.text())
+    const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.svg`)
+    expect(statusCode).to.equal(200)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('fruit')
       .and.to.include('apple')
   })
 
   it('should produce colorscheme PNG badges', async function() {
-    const res = await fetch(`${baseUrl}:fruit-apple-green.png`)
-    expect(res.ok).to.be.true
-    expect(await res.buffer()).to.satisfy(isPng)
+    const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.png`, {
+      encoding: null,
+    })
+    expect(statusCode).to.equal(200)
+    expect(body).to.satisfy(isPng)
   })
 
   it('should preserve label case', async function() {
-    const res = await fetch(`${baseUrl}:fRuiT-apple-green.svg`)
-    expect(res.ok).to.be.true
-    expect(await res.text())
+    const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
+    expect(statusCode).to.equal(200)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('fRuiT')
   })
 
   // https://github.com/badges/shields/pull/1319
   it('should not crash with a numeric logo', async function() {
-    const res = await fetch(`${baseUrl}:fruit-apple-green.svg?logo=1`)
-    expect(res.ok).to.be.true
-    expect(await res.text())
+    const { statusCode, body } = await got(
+      `${baseUrl}:fruit-apple-green.svg?logo=1`
+    )
+    expect(statusCode).to.equal(200)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('fruit')
       .and.to.include('apple')
   })
 
   it('should not crash with a numeric link', async function() {
-    const res = await fetch(`${baseUrl}:fruit-apple-green.svg?link=1`)
-    expect(res.ok).to.be.true
-    expect(await res.text())
+    const { statusCode, body } = await got(
+      `${baseUrl}:fruit-apple-green.svg?link=1`
+    )
+    expect(statusCode).to.equal(200)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('fruit')
       .and.to.include('apple')
   })
 
   it('should not crash with a boolean link', async function() {
-    const res = await fetch(`${baseUrl}:fruit-apple-green.svg?link=true`)
-    expect(res.ok).to.be.true
-    expect(await res.text())
+    const { statusCode, body } = await got(
+      `${baseUrl}:fruit-apple-green.svg?link=true`
+    )
+    expect(statusCode).to.equal(200)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('fruit')
       .and.to.include('apple')
   })
 
   it('should return the 404 badge for unknown badges', async function() {
-    const res = await fetch(`${baseUrl}this/is/not/a/badge.svg`)
-    expect(res.status).to.equal(404)
-    expect(await res.text())
+    const { statusCode, body } = await got(
+      `${baseUrl}this/is/not/a/badge.svg`,
+      { throwHttpErrors: false }
+    )
+    expect(statusCode).to.equal(404)
+    expect(body)
       .to.satisfy(isSvg)
       .and.to.include('404')
       .and.to.include('badge not found')
   })
 
   it('should return the 404 html page for rando links', async function() {
-    const res = await fetch(`${baseUrl}this/is/most/definitely/not/a/badge.js`)
-    expect(res.status).to.equal(404)
-    expect(await res.text()).to.include('blood, toil, tears and sweat')
+    const { statusCode, body } = await got(
+      `${baseUrl}this/is/most/definitely/not/a/badge.js`,
+      { throwHttpErrors: false }
+    )
+    expect(statusCode).to.equal(404)
+    expect(body).to.include('blood, toil, tears and sweat')
   })
 
   it('should redirect the root as configured', async function() {
-    const res = await got(baseUrl, { followRedirect: false })
+    const { statusCode, headers } = await got(baseUrl, {
+      followRedirect: false,
+    })
 
-    expect(res.statusCode).to.equal(302)
+    expect(statusCode).to.equal(302)
     // This value is set in `config/test.yml`
-    expect(res.headers.location).to.equal('http://badge-server.example.com')
+    expect(headers.location).to.equal('http://badge-server.example.com')
   })
 
   context('with svg2img error', function() {
@@ -119,10 +135,12 @@ describe('The server', function() {
     })
 
     it('should emit the 500 message', async function() {
-      const res = await fetch(`${baseUrl}:some_new-badge-green.png`)
+      const { statusCode, body } = await got(
+        `${baseUrl}:some_new-badge-green.png`
+      )
       // This emits status code 200, though 500 would be preferable.
-      expect(res.status).to.equal(200)
-      expect(await res.text()).to.include(expectedError)
+      expect(statusCode).to.equal(200)
+      expect(body).to.include(expectedError)
     })
   })
 })
diff --git a/core/service-test-runner/pull-request-services-cli.js b/core/service-test-runner/pull-request-services-cli.js
index 0818f21b235e5352270c757cb926aff940d70e9e..3f61394f325342c0cb434004f2529e3f58ab3682 100644
--- a/core/service-test-runner/pull-request-services-cli.js
+++ b/core/service-test-runner/pull-request-services-cli.js
@@ -15,22 +15,21 @@
 
 'use strict'
 
-const fetch = require('node-fetch')
+const got = require('got')
 const { inferPullRequest } = require('./infer-pull-request')
 const servicesForTitle = require('./services-for-title')
 
 async function getTitle(owner, repo, pullRequest) {
-  let uri = `https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}`
-  if (process.env.GITHUB_TOKEN) {
-    uri += `?access_token=${process.env.GITHUB_TOKEN}`
-  }
-  const options = { headers: { 'User-Agent': 'badges/shields' } }
-  const res = await fetch(uri, options)
-  if (!res.ok) {
-    throw Error(`${res.status} ${res.statusText}`)
-  }
-
-  const { title } = await res.json()
+  const {
+    body: { title },
+  } = await got(
+    `https://api.github.com/repos/${owner}/${repo}/pulls/${pullRequest}`,
+    {
+      headers: { 'User-Agent': 'badges/shields' },
+      query: { access_token: process.env.GITHUB_TOKEN },
+      json: true,
+    }
+  )
   return title
 }
 
diff --git a/entrypoint.spec.js b/entrypoint.spec.js
index 53f39832cd0214a61ab3ab866b1736e176bacc9e..e24ba4f848d331ef20b2be48f49e075637e663f5 100644
--- a/entrypoint.spec.js
+++ b/entrypoint.spec.js
@@ -1,7 +1,8 @@
 'use strict'
 
 const { expect } = require('chai')
-const fetch = require('node-fetch')
+// https://github.com/nock/nock/issues/1523
+const got = require('got').extend({ retry: 0 })
 const isSvg = require('is-svg')
 
 let server
@@ -18,9 +19,11 @@ after('shut down the server', async function() {
 })
 
 it('should render a badge', async function() {
-  const res = await fetch('http://localhost:1111/badge/fruit-apple-green.svg')
-  expect(res.ok).to.be.true
-  expect(await res.text())
+  const { statusCode, body } = await got(
+    'http://localhost:1111/badge/fruit-apple-green.svg'
+  )
+  expect(statusCode).to.equal(200)
+  expect(body)
     .to.satisfy(isSvg)
     .and.to.include('fruit')
     .and.to.include('apple')
diff --git a/package.json b/package.json
index 035a93041312c4b5fd2ce93751a0d267247116e2..1c01e2c266a4c4b42b87661d9075ef2a78c6be3c 100644
--- a/package.json
+++ b/package.json
@@ -207,7 +207,6 @@
     "mocha-junit-reporter": "^1.21.0",
     "mocha-yaml-loader": "^1.0.3",
     "nock": "11.0.0-beta.10",
-    "node-fetch": "^2.3.0",
     "node-mocks-http": "^1.7.3",
     "nodemon": "^1.19.0",
     "npm-run-all": "^4.1.5",
diff --git a/services/github/auth/admin.spec.js b/services/github/auth/admin.spec.js
index 9d9506997e6f4d0e0179323a703924b8bb1608c3..62827bf282c31f473e68a68a65d38702aecd6cd6 100644
--- a/services/github/auth/admin.spec.js
+++ b/services/github/auth/admin.spec.js
@@ -3,19 +3,13 @@
 const { expect } = require('chai')
 const sinon = require('sinon')
 const Camp = require('camp')
-const fetch = require('node-fetch')
+// https://github.com/nock/nock/issues/1523
+const got = require('got').extend({ retry: 0 })
 const portfinder = require('portfinder')
 const serverSecrets = require('../../../lib/server-secrets')
 const GithubApiProvider = require('../github-api-provider')
 const { setRoutes } = require('./admin')
 
-function createAuthHeader({ username, password }) {
-  const headers = new fetch.Headers()
-  const encoded = Buffer.from(`${username}:${password}`).toString('base64')
-  headers.append('authorization', `Basic ${encoded}`)
-  return headers
-}
-
 describe('GitHub admin route', function() {
   const validCredentials = {
     username: '',
@@ -60,11 +54,13 @@ describe('GitHub admin route', function() {
 
   context('the password is correct', function() {
     it('returns a valid JSON response', async function() {
-      const res = await fetch(`${baseUrl}/$github-auth/tokens`, {
-        headers: createAuthHeader(validCredentials),
+      const { username, password } = validCredentials
+      const { statusCode, body } = await got(`${baseUrl}/$github-auth/tokens`, {
+        auth: `${username}:${password}`,
+        json: true,
       })
-      expect(res.ok).to.be.true
-      expect(await res.json()).to.be.ok
+      expect(statusCode).to.equal(200)
+      expect(body).to.be.ok
     })
   })