# Move a Discourse site to another VPS with rsync

**URL:** https://meta.discourse.org/t/move-a-discourse-site-to-another-vps-with-rsync/43812
**Category:** Sysadmins
**Tags:** migrations, how-to
**Created:** [May 6, 2016, 8:21pm UTC](https://meta.discourse.org/t/move-a-discourse-site-to-another-vps-with-rsync/43812 "2016-05-06T20:21:38Z")
**Posts on this page:** 1
**Showing post:** 57

<div class="post-metadata">

### Author: ![icculus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/icculus/32/120985_2.png) [@icculus](https://meta.discourse.org/u/icculus)
#### Post date: [August 17, 2025, 10:20pm UTC](https://meta.discourse.org/t/move-a-discourse-site-to-another-vps-with-rsync/43812/57 "2025-08-17T22:20:11Z")

</div>

I got this done, and Postgres (and Discourse!) seem to be happy.

I cleaned them out by hand, making the URLs unique with `**` patterns as appropriate. It might just be a harmless cache where I could have deleted the duplicates but I didn’t want to risk it.

In my case, it was just the one index, so rebuilding _all_ the indices was probably overkill, but honestly I felt better knowing I caught everything.

After a few failed runs of rebuilding, which takes 30 seconds or so each time and reports a single problem, this was my SQL magic to get a complete list of problem items instantly:

```plaintext
discourse=# select topic_id, post_id, url, COUNT(*) from topic_links GROUP BY topic_id, post_id, url HAVING COUNT(*) > 1 order by topic_id, post_id;
 topic_id | post_id | url | count 
----------+---------+-------------------------------------------------------+-------
    19200 | 88461 | http://hg.libsdl.org/SDL/rev/**533131e24aeb | 2
    19207 | 88521 | http://hg.libsdl.org/SDL/rev/44a2e00e7c66 | 2
    19255 | 88683 | http://lists.libsdl.org/__listinfo.cgi/sdl-libsdl.org | 2
    19255 | 88683 | http://lists.libsdl.org/**listinfo.cgi/sdl-libsdl.org | 2
    19523 | 90003 | http://twitter.com/Ironcode_Gaming | 2
(5 rows)

```

(5 remaining problem items in this query, for example purposes.)

Then I’d look at each post to see what was there and what to fix up:

```plaintext
select * from topic_links where topic_id=19255 and post_id=88683

```

and then fix one of them up:

```plaintext
update public.topic_links set url='http://lists.libsdl.org/__listinfo.cgi/**sdl-libsdl.org' where id=275100;

```

Until I ran out of things to fix up. 🙂

I probably could have done some inner-join magic (or maybe a little Ruby) to get this all in one query, but I’m not an expert and it turned out to not be _hours_ of work to do it manually. But it _was_ tedious, to be clear. 🙂

Then I did `REINDEX DATABASE discourse;` without the `CONCURRENTLY` just to keep it simple, nuked a few `ccnew*` indices I had missed earlier, and I was good to go.

Site was live the whole time, no downtime.

Whether this was _necessary_ or not, I _definitely_ feel like my data is a little safer now, and I’m not careening towards some unannounced future disaster.

Thanks for nudging me in the right direction to figure this out, @pfaffman!

---

_[View the full topic](https://meta.discourse.org/t/move-a-discourse-site-to-another-vps-with-rsync/43812)._
