diff --git a/doc/release-notes.md b/doc/release-notes.md
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -10,3 +10,12 @@
  - `-usehd` was removed in version 0.16. From that version onwards, all new
  wallets created are hierarchical deterministic wallets. Version 0.18 makes
  specifying `-usehd` invalid config.
+
+Seeder
+------
+  - Seeder now requires nodes to have the block after the most recent checkpoint
+    in its chain in order to be considered a good seed.
+  - Nodes that are undergoing IBD are considered bad nodes until they're chain
+    reaches the block after the most recent checkpoint.
+  - Nodes following the wrong chain are considered bad nodes and temporarily
+    banned.
diff --git a/src/seeder/bitcoin.h b/src/seeder/bitcoin.h
--- a/src/seeder/bitcoin.h
+++ b/src/seeder/bitcoin.h
@@ -16,6 +16,8 @@
     return Params().GetDefaultPort();
 }
 
+static const unsigned int MAX_HEADERS_PER_MESSAGE = 2000;
+
 // The network magic to use.
 extern CMessageHeader::MessageMagic netMagic;
 
diff --git a/src/seeder/bitcoin.cpp b/src/seeder/bitcoin.cpp
--- a/src/seeder/bitcoin.cpp
+++ b/src/seeder/bitcoin.cpp
@@ -114,18 +114,71 @@
 
     if (strCommand == "verack") {
         vRecv.SetVersion(std::min(nVersion, PROTOCOL_VERSION));
-        // fprintf(stdout, "\n%s: version %i\n", ToString(you).c_str(),
-        // nVersion);
-        if (vAddr) {
-            BeginMessage("getaddr");
-            EndMessage();
-            doneAfter = time(nullptr) + GetTimeout();
-        } else {
-            doneAfter = time(nullptr) + 1;
-        }
+        // Create a getheaders message
+        BeginMessage("getheaders");
+        // Locator hash: Seeder requests the headers after the latest checkpoint
+        BlockHash seederCheckpoint =
+            Params().Checkpoints().mapCheckpoints.rbegin()->second;
+        std::vector<uint8_t> hashStop(32, 0);
+        uint8_t numberOfLocatorHashes = 1;
+        vSend << PROTOCOL_VERSION << numberOfLocatorHashes << seederCheckpoint
+              << uint256(hashStop);
+        EndMessage();
+        // Update socket timeout
+        doneAfter = time(nullptr) + GetTimeout();
         return PeerMessagingState::AwaitingMessages;
     }
 
+    if (strCommand == NetMsgType::HEADERS) {
+        uint64_t nHeaders = ReadCompactSize(recv);
+        // Peer node did not have the checkpoint block requested by the seeder
+        // Peer only has up to the checkpoint or only has the genesis block.
+        // Since there is no way to tell the difference mark the peer as bad,
+        // but don't ban.
+        if (nHeaders == 0) {
+            doneAfter = 0;
+            return PeerMessagingState::Finished;
+        }
+        // Peer node sent a malformed headers message
+        if (nHeaders > MAX_HEADERS_PER_MESSAGE) {
+            ban = 100000;
+            return PeerMessagingState::Finished;
+        }
+        CBlockHeader header;
+        recv >> header;
+        /**
+         * If the node has this checkpoint then it will return the first
+         * MAX_HEADERS_PER_MESSAGE block headers it knows that come after the
+         * checkpoint.  This means the hashPrevBlock of the first header
+         * received should be the hash of the checkpoint if the node is
+         * following the correct chain.
+         */
+        if (Params().Checkpoints().mapCheckpoints.rbegin()->second ==
+            header.hashPrevBlock) {
+            if (vAddr) {
+                BeginMessage("getaddr");
+                EndMessage();
+                doneAfter = time(nullptr) + GetTimeout();
+            } else {
+                doneAfter = time(nullptr) + 1;
+            }
+            return PeerMessagingState::AwaitingMessages;
+        }
+        // Peer is currently undergoing IBD, mark as bad but don't ban.
+        if (Params().GenesisBlock().GetBlockHeader().GetHash() ==
+            header.hashPrevBlock) {
+            doneAfter = 0;
+            return PeerMessagingState::Finished;
+        }
+
+        /**
+         * Conversely, if hashPrevBlock is not the checkpoint, then the node is
+         * not following the correct chain and should be banned.
+         */
+        ban = 100000;
+        return PeerMessagingState::Finished;
+    }
+
     if (strCommand == "addr" && vAddr) {
         std::vector<CAddress> vAddrNew;
         recv >> vAddrNew;
diff --git a/src/seeder/test/p2p_messaging_tests.cpp b/src/seeder/test/p2p_messaging_tests.cpp
--- a/src/seeder/test/p2p_messaging_tests.cpp
+++ b/src/seeder/test/p2p_messaging_tests.cpp
@@ -5,6 +5,7 @@
 #define BOOST_TEST_MODULE Bitcoin Seeder Test Suite
 
 #include <chainparams.h>
+#include <hash.h>
 #include <protocol.h>
 #include <seeder/bitcoin.h>
 #include <seeder/db.h>
@@ -32,7 +33,7 @@
 public:
     TestCSeederNode(const CService &service, std::vector<CAddress> *vAddrIn)
         : CSeederNode(service, vAddrIn) {
-        SelectParams(CBaseChainParams::REGTEST);
+        SelectParams(CBaseChainParams::MAIN);
     }
 
     void TestProcessMessage(const std::string &strCommand, CDataStream &message,
@@ -139,4 +140,96 @@
     }
 }
 
+enum HeaderMessageType {
+    WRONG_CHAIN_HEADER,
+    GOOD_HEADER,
+    NO_HEADERS,
+    TOO_MANY_HEADERS,
+    IBD,
+};
+
+static CDataStream CreateHeadersMessage(HeaderMessageType headersMessageContent,
+                                        int32_t nVersion = INIT_PROTO_VERSION,
+                                        uint32_t timeStamp = 0,
+                                        uint32_t bits = 0, uint32_t nonce = 0) {
+    CDataStream payload(SER_NETWORK, nVersion);
+    if (headersMessageContent == HeaderMessageType::NO_HEADERS) {
+        payload << 0;
+        return payload;
+    }
+    uint16_t numberOfHeaders = 1;
+    if (headersMessageContent == HeaderMessageType::TOO_MANY_HEADERS) {
+        numberOfHeaders = MAX_HEADERS_PER_MESSAGE + 1;
+    }
+    // The actual header can be garbage data, Seeder only cares that it got
+    // a specific type of message, the size, and the hash of the previous block.
+    uint256 garbageHash = Hash(payload.begin(), payload.end());
+    uint256 merkleRoot = garbageHash;
+    BlockHash previousBlockHash =
+        Params().Checkpoints().mapCheckpoints.rbegin()->second;
+    if (headersMessageContent == HeaderMessageType::WRONG_CHAIN_HEADER) {
+        previousBlockHash = BlockHash(garbageHash);
+    } else if (headersMessageContent == HeaderMessageType::IBD) {
+        previousBlockHash = Params().GenesisBlock().GetBlockHeader().GetHash();
+    }
+
+    CBlockHeader header;
+    header.nVersion = nVersion;
+    header.hashPrevBlock = previousBlockHash;
+    header.hashMerkleRoot = merkleRoot;
+    header.nTime = timeStamp;
+    header.nBits = bits;
+    header.nNonce = nonce;
+    // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx
+    // count at the end
+    std::vector<CBlock> headers(numberOfHeaders, CBlock(header));
+    payload << headers;
+    return payload;
+}
+
+BOOST_AUTO_TEST_CASE(seeder_node_headers_test_happy_path) {
+    // Happy path
+    CDataStream headersMessage =
+        CreateHeadersMessage(HeaderMessageType::GOOD_HEADER);
+    testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage,
+                                 PeerMessagingState::AwaitingMessages);
+    BOOST_CHECK_EQUAL(testNode->GetBan(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(seeder_node_headers_wrong_chain_test) {
+    // Seeder receives a HEADERS message following the wrong chain
+    CDataStream headersMessage =
+        CreateHeadersMessage(HeaderMessageType::WRONG_CHAIN_HEADER);
+    testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage,
+                                 PeerMessagingState::Finished);
+    BOOST_CHECK_EQUAL(testNode->GetBan(), 100000);
+}
+
+BOOST_AUTO_TEST_CASE(seeder_node_empty_headers_test) {
+    // Seeder receives an empty HEADERS message
+    CDataStream headersMessage =
+        CreateHeadersMessage(HeaderMessageType::NO_HEADERS);
+    testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage,
+                                 PeerMessagingState::Finished);
+    BOOST_CHECK_EQUAL(testNode->GetBan(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(seeder_node_too_many_headers_test) {
+    // Seeder receives a HEADERS message with too many headers
+    CDataStream headersMessage =
+        CreateHeadersMessage(HeaderMessageType::TOO_MANY_HEADERS);
+    testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage,
+                                 PeerMessagingState::Finished);
+    BOOST_CHECK_EQUAL(testNode->GetBan(), 100000);
+}
+
+BOOST_AUTO_TEST_CASE(seeder_node_ibd_headers_test) {
+    // Seeder receives a HEADERS message with the first header belonging to the
+    // block that comes after the genesis block
+    CDataStream headersMessage = CreateHeadersMessage(HeaderMessageType::IBD);
+    testNode->TestProcessMessage(NetMsgType::HEADERS, headersMessage,
+                                 PeerMessagingState::Finished);
+    BOOST_CHECK_EQUAL(testNode->GetBan(), 0);
+}
+
 BOOST_AUTO_TEST_SUITE_END()