diff --git a/src/cell.c b/src/cell.c
index 8bd26b32343f2307673c5f0d9c3abfd68172c187..66f7453667e202494515e0eb45ab7028e75a8303 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -1386,12 +1386,14 @@ void cell_check_sort_flags(const struct cell *c) {
   const int do_stars_sub_sort = cell_get_flag(c, cell_flag_do_stars_sub_sort);
 
   if (do_hydro_sub_sort)
-    error("cell %d has a hydro sub_sort flag set. Node=%d depth=%d maxdepth=%d",
-          c->cellID, c->nodeID, c->depth, c->maxdepth);
+    error(
+        "cell %lld has a hydro sub_sort flag set. Node=%d depth=%d maxdepth=%d",
+        c->cellID, c->nodeID, c->depth, c->maxdepth);
 
   if (do_stars_sub_sort)
-    error("cell %d has a stars sub_sort flag set. Node=%d depth=%d maxdepth=%d",
-          c->cellID, c->nodeID, c->depth, c->maxdepth);
+    error(
+        "cell %lld has a stars sub_sort flag set. Node=%d depth=%d maxdepth=%d",
+        c->cellID, c->nodeID, c->depth, c->maxdepth);
 
   if (c->split) {
     for (int k = 0; k < 8; ++k) {
diff --git a/src/cell.h b/src/cell.h
index d80d11b8229032795a64b2c862d3dc0bc310de70..b6d9bb9ad66ca30bbd55839d59f40986d404e224 100644
--- a/src/cell.h
+++ b/src/cell.h
@@ -62,6 +62,11 @@ struct scheduler;
 /* Global variables. */
 extern int cell_next_tag;
 
+/*! Counter for cell IDs (when exceeding max values for uniqueness) */
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+extern long long last_cell_id;
+#endif
+
 /* Struct to temporarily buffer the particle locations and bin id. */
 struct cell_buff {
   double x[3];
@@ -225,7 +230,7 @@ struct pcell {
 
 #ifdef SWIFT_DEBUG_CHECKS
   /* Cell ID (for debugging) */
-  int cellID;
+  long long cellID;
 #endif
 
 } SWIFT_STRUCT_ALIGN;
@@ -452,7 +457,7 @@ struct cell {
 
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
   /* Cell ID (for debugging) */
-  int cellID;
+  long long cellID;
 #endif
 
 #ifdef SWIFT_DEBUG_CHECKS
@@ -1279,4 +1284,95 @@ __attribute__((always_inline)) INLINE static struct task *cell_get_recv(
 #endif
 }
 
+/**
+ * @brief Generate the cell ID for top level cells. Only used for debugging.
+ *
+ * Cell IDs are stored in the long long `cell->cellID`. Top level cells get
+ * their index according to their location on the top level grid, and are
+ * marked with a minus sign.
+ * We have 15 bits set aside in `cell->cellID` for the top level cells. Hence
+ * if we have more that 32^3 top level cells, the cell IDs won't be guaranteed
+ * to be unique. Top level cells will still be recognizable by the minus sign.
+ */
+__attribute__((always_inline)) INLINE void cell_assign_top_level_cell_index(
+    struct cell *c, int cdim[3], double dim[3], double width[3]) {
+
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+  if (c->depth != 0) {
+    error("assigning top level cell index to cell with depth > 0");
+  } else {
+    if (cdim[0] * cdim[1] * cdim[2] > 32 * 32 * 32) {
+      /* print warning only once */
+      if (last_cell_id == 1) {
+        message(
+            "Warning: Got %d x %d x %d top level cells."
+            "Cell IDs are only guaranteed to be unique if count is < 32^3",
+            cdim[0], cdim[1], cdim[2]);
+      }
+      c->cellID = -last_cell_id;
+      atomic_inc(&last_cell_id);
+    }
+
+    int i = (int)(c->loc[0] / width[0]);
+    int j = (int)(c->loc[1] / width[1]);
+    int k = (int)(c->loc[2] / width[2]);
+    c->cellID = -(long long)(cell_getid(cdim, i, j, k) + 1);
+  }
+#endif
+}
+
+/**
+ * @brief Generate the cell ID for progeny cells. Only used for debugging.
+ *
+ * Cell IDs are stored in the long long `cell->cellID`.
+ * We have 15 bits set aside in `cell->cellID` for the top level cells, and
+ * one for a minus sign to mark top level cells. The remaining 48 bits are
+ * for all other cells. Each progeny cell gets a unique ID by inheriting
+ * its parent ID and adding 3 bits on the right side, which are set according
+ * to the progeny's location within its parent cell. Hence we can store up to
+ * 16 levels of depth uniquely.
+ * If the depth exceeds 16, we use the old scheme where we just add up a
+ * counter. This gives us 32^3 new unique cell IDs, previously reserved for
+ * top level cells, but the IDs won't be thread safe and will vary each run.
+ * After the 32^3 cells are filled, we reach degeneracy.
+ */
+__attribute__((always_inline)) INLINE void cell_assign_cell_index(
+    struct cell *c, const struct cell *parent) {
+
+#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
+  if (c->depth == 0) {
+    error("assigning progeny cell index to top level cell.");
+  } else if (c->depth > 16 || last_cell_id > 1) {
+    /* last_cell_id > 1 => too many top level cells for clever IDs */
+    /* print warning only once */
+    if (last_cell_id == 1) {
+      message(
+          "Warning: Got depth %d > 16."
+          "IDs are only guaranteed unique if depth <= 16",
+          c->depth);
+    }
+    c->cellID = last_cell_id;
+    atomic_inc(&last_cell_id);
+  } else {
+    /* we're good to go for unique IDs */
+    /* first inherit the parent's ID and mark it as not top-level*/
+    long long child_id = llabs(parent->cellID);
+
+    /* make place for new bits */
+    /* parent's ID needs to be leading bits, so 000 children still
+     * change the value of the cellID */
+    child_id <<= 3;
+
+    /* get progeny index in parent cell */
+    if (c->loc[0] > parent->loc[0]) child_id |= 1LL;
+    if (c->loc[1] > parent->loc[1]) child_id |= 2LL;
+    if (c->loc[2] > parent->loc[2]) child_id |= 4LL;
+
+    /* add progeny index to cell index */
+    c->cellID = child_id;
+  }
+
+#endif
+}
+
 #endif /* SWIFT_CELL_H */
diff --git a/src/runner_doiact_functions_stars.h b/src/runner_doiact_functions_stars.h
index 7e802d6f4afd885697c3915d475d38a4e5da7a73..b8bea6fa2125bbddb0deb013c46876e97ec61927 100644
--- a/src/runner_doiact_functions_stars.h
+++ b/src/runner_doiact_functions_stars.h
@@ -1098,7 +1098,7 @@ void DOSELF1_BRANCH_STARS(struct runner *r, struct cell *c) {
             "ci->nodeID=%d d=%e sort_j[pjd].d=%e cj->" #TYPE                \
             ".dx_max_sort=%e "                                              \
             "cj->" #TYPE                                                    \
-            ".dx_max_sort_old=%e, cellID=%i super->cellID=%i"               \
+            ".dx_max_sort_old=%e, cellID=%lld super->cellID=%lld"           \
             "cj->depth=%d cj->maxdepth=%d",                                 \
             cj->nodeID, ci->nodeID, d, sort_j[pjd].d, cj->TYPE.dx_max_sort, \
             cj->TYPE.dx_max_sort_old, cj->cellID, cj->hydro.super->cellID,  \
diff --git a/src/runner_others.c b/src/runner_others.c
index f684b1714f4eaad4d5c8b9c87c162d7f9ef89b21..479da517a7e435d91161ba4397d7acda9dbd76fe 100644
--- a/src/runner_others.c
+++ b/src/runner_others.c
@@ -313,7 +313,7 @@ void runner_do_star_formation(struct runner *r, struct cell *c, int timer) {
             /* Did we get a star? (Or did we run out of spare ones?) */
             if (sp != NULL) {
 
-              /* message("We formed a star id=%lld cellID=%d", sp->id,
+              /* message("We formed a star id=%lld cellID=%lld", sp->id,
                * c->cellID); */
 
               /* Copy the properties of the gas particle to the star particle */
diff --git a/src/runner_time_integration.c b/src/runner_time_integration.c
index 656490bd3491a5166936af682623e6de668b28fc..db7f4a23ba743683137308680381ff86cc5608ba 100644
--- a/src/runner_time_integration.c
+++ b/src/runner_time_integration.c
@@ -1178,7 +1178,7 @@ void runner_do_limiter(struct runner *r, struct cell *c, int force,
       /* Bip, bip, bip... wake-up time */
       if (p->limiter_data.wakeup != time_bin_not_awake) {
 
-        // message("Limiting particle %lld in cell %d", p->id, c->cellID);
+        // message("Limiting particle %lld in cell %lld", p->id, c->cellID);
 
         /* Apply the limiter and get the new end of time-step */
         const integertime_t ti_end_new = timestep_limit_part(p, xp, e);
diff --git a/src/space.c b/src/space.c
index 6747524f4fc0c33d5eb9b0392ba88cb8a85a78ae..5a5e11e541d599589a352aa0b32cb6564b474fad 100644
--- a/src/space.c
+++ b/src/space.c
@@ -94,9 +94,9 @@ int engine_star_resort_task_depth = engine_star_resort_task_depth_default;
 /*! Expected maximal number of strays received at a rebuild */
 int space_expected_max_nr_strays = space_expected_max_nr_strays_default;
 
-/*! Counter for cell IDs (when debugging) */
+/*! Counter for cell IDs (when debugging + max vals for unique IDs exceeded) */
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-int last_cell_id;
+long long last_cell_id;
 #endif
 
 /**
@@ -2412,24 +2412,24 @@ void space_write_cell(const struct space *s, FILE *f, const struct cell *c) {
   if (c == NULL) return;
 
   /* Get parent ID */
-  int parent = root_cell_id;
+  long long parent = root_cell_id;
   if (c->parent != NULL) parent = c->parent->cellID;
 
   /* Get super ID */
   char superID[100] = "";
-  if (c->super != NULL) sprintf(superID, "%i", c->super->cellID);
+  if (c->super != NULL) sprintf(superID, "%lld", c->super->cellID);
 
   /* Get hydro super ID */
   char hydro_superID[100] = "";
   if (c->hydro.super != NULL)
-    sprintf(hydro_superID, "%i", c->hydro.super->cellID);
+    sprintf(hydro_superID, "%lld", c->hydro.super->cellID);
 
   /* Write line for current cell */
-  fprintf(f, "%i,%i,%i,", c->cellID, parent, c->nodeID);
+  fprintf(f, "%lld,%lld,%i,", c->cellID, parent, c->nodeID);
   fprintf(f, "%i,%i,%i,%s,%s,%g,%g,%g,%g,%g,%g, ", c->hydro.count,
           c->stars.count, c->grav.count, superID, hydro_superID, c->loc[0],
           c->loc[1], c->loc[2], c->width[0], c->width[1], c->width[2]);
-  fprintf(f, "%g, %g %i %i\n", c->hydro.h_max, c->stars.h_max, c->depth,
+  fprintf(f, "%g, %g, %i, %i\n", c->hydro.h_max, c->stars.h_max, c->depth,
           c->maxdepth);
 
   /* Write children */
@@ -2466,9 +2466,9 @@ void space_write_cell_hierarchy(const struct space *s, int j) {
 
     /* Write root data */
     fprintf(f, "%i, ,-1,", root_id);
-    fprintf(f, "%li,%li,%li, , , , , , , , , ", s->nr_parts, s->nr_sparts,
+    fprintf(f, "%li,%li,%li, , , , , , , , ,", s->nr_parts, s->nr_sparts,
             s->nr_gparts);
-    fprintf(f, ",\n");
+    fprintf(f, " , , ,\n");
   }
 
   /* Write all the top level cells (and their children) */
diff --git a/src/space_rebuild.c b/src/space_rebuild.c
index f456be124731830a7c91f65caca4d1517644d701..0b68ca2b0cf9cfaea16ba95908fa38cacff71a25 100644
--- a/src/space_rebuild.c
+++ b/src/space_rebuild.c
@@ -35,7 +35,7 @@ extern int space_expected_max_nr_strays;
 
 /*! Counter for cell IDs (when debugging) */
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-extern int last_cell_id;
+extern long long last_cell_id;
 #endif
 
 /**
@@ -911,8 +911,7 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
     c->black_holes.ti_old_part = ti_current;
 
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-    c->cellID = -last_cell_id;
-    last_cell_id++;
+    cell_assign_top_level_cell_index(c, s->cdim, s->dim, s->width);
 #endif
 
     const int is_local = (c->nodeID == engine_rank);
diff --git a/src/space_regrid.c b/src/space_regrid.c
index b20a3b4894bf3ce6418cd288f74fe91cedc36ab7..035c3f92096ab16c9334c7e56e791bf06b53c528 100644
--- a/src/space_regrid.c
+++ b/src/space_regrid.c
@@ -30,11 +30,6 @@
 #include "engine.h"
 #include "scheduler.h"
 
-/*! Counter for cell IDs (when debugging) */
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-extern int last_cell_id;
-#endif
-
 /**
  * @brief Re-build the top-level cell grid.
  *
@@ -321,8 +316,7 @@ void space_regrid(struct space *s, int verbose) {
 #endif  // WITH_MPI
           if (s->with_self_gravity) c->grav.multipole = &s->multipoles_top[cid];
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-          c->cellID = -last_cell_id;
-          last_cell_id++;
+          cell_assign_top_level_cell_index(c, s->cdim, s->dim, s->width);
 #endif
         }
 
diff --git a/src/space_split.c b/src/space_split.c
index 728c6f8984b3476bfc0729546059477c3d1f38cf..38d75ad48b3e10c21d7047bb6471f41ee16bd881 100644
--- a/src/space_split.c
+++ b/src/space_split.c
@@ -33,11 +33,6 @@
 #include "star_formation_logger.h"
 #include "threadpool.h"
 
-/*! Counter for cell IDs (when debugging) */
-#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-extern int last_cell_id;
-#endif
-
 /**
  * @brief Recursively split a cell.
  *
@@ -250,7 +245,7 @@ void space_split_recursive(struct space *s, struct cell *c,
       cp->mpi.tag = -1;
 #endif  // WITH_MPI
 #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
-      cp->cellID = last_cell_id++;
+      cell_assign_cell_index(cp, c);
 #endif
     }